xref: /openbmc/bmcweb/scripts/parse_registries.py (revision 7196de921837cca6eb3a2250a2cec22c093d667f)
1#!/usr/bin/env python3
2import argparse
3import json
4import os
5import typing as t
6from collections import OrderedDict
7
8import requests
9
10PRAGMA_ONCE: t.Final[str] = """#pragma once
11"""
12
13WARNING = """/****************************************************************
14 *                 READ THIS WARNING FIRST
15 * This is an auto-generated header which contains definitions
16 * for Redfish DMTF defined messages.
17 * DO NOT modify this registry outside of running the
18 * parse_registries.py script.  The definitions contained within
19 * this file are owned by DMTF.  Any modifications to these files
20 * should be first pushed to the relevant registry in the DMTF
21 * github organization.
22 ***************************************************************/"""
23
24COPYRIGHT: t.Final[str] = """// SPDX-License-Identifier: Apache-2.0
25// SPDX-FileCopyrightText: Copyright OpenBMC Authors
26"""
27
28INCLUDES = """
29#include "registries.hpp"
30
31#include <array>
32
33// clang-format off
34
35namespace redfish::registries
36{{
37struct {}
38{{
39"""
40
41REGISTRY_HEADER: t.Final[str] = f"{COPYRIGHT}{PRAGMA_ONCE}{WARNING}{INCLUDES}"
42
43SCRIPT_DIR: t.Final[str] = os.path.dirname(os.path.realpath(__file__))
44
45INCLUDE_PATH: t.Final[str] = os.path.realpath(
46    os.path.join(SCRIPT_DIR, "..", "redfish-core", "include", "registries")
47)
48
49PROXIES: t.Final[t.Dict[str, str]] = {
50    "https": os.environ.get("https_proxy", "")
51}
52
53RegistryInfo: t.TypeAlias = t.Tuple[str, t.Dict[str, t.Any], str, str]
54
55
56def make_getter(
57    dmtf_name: str, header_name: str, type_name: str
58) -> RegistryInfo:
59    url = "https://redfish.dmtf.org/registries/{}".format(dmtf_name)
60    dmtf = requests.get(url, proxies=PROXIES)
61    dmtf.raise_for_status()
62    json_file = json.loads(dmtf.text, object_pairs_hook=OrderedDict)
63    path = os.path.join(INCLUDE_PATH, header_name)
64    return (path, json_file, type_name, url)
65
66
67def openbmc_local_getter() -> RegistryInfo:
68    url = "https://raw.githubusercontent.com/openbmc/bmcweb/refs/heads/master/redfish-core/include/registries/openbmc.json"
69    with open(
70        os.path.join(
71            SCRIPT_DIR,
72            "..",
73            "redfish-core",
74            "include",
75            "registries",
76            "openbmc.json",
77        ),
78        "rb",
79    ) as json_file_fd:
80        json_file = json.load(json_file_fd)
81
82    path = os.path.join(INCLUDE_PATH, "openbmc_message_registry.hpp")
83    return (path, json_file, "openbmc", url)
84
85
86def update_registries(files: t.List[RegistryInfo]) -> None:
87    # Remove the old files
88    for file, json_dict, namespace, url in files:
89        try:
90            os.remove(file)
91        except BaseException:
92            print("{} not found".format(file))
93
94        with open(file, "w") as registry:
95            version_split = json_dict["RegistryVersion"].split(".")
96
97            struct_name = to_pascal_case(namespace)
98            registry.write(REGISTRY_HEADER.format(struct_name))
99            # Parse the Registry header info
100            description = json_dict.get("Description", "")
101            registry.write(
102                "static constexpr Header header = {{\n"
103                '    "{json_dict[@Redfish.Copyright]}",\n'
104                '    "{json_dict[@odata.type]}",\n'
105                "    {version_split[0]},\n"
106                "    {version_split[1]},\n"
107                "    {version_split[2]},\n"
108                '    "{json_dict[Name]}",\n'
109                '    "{json_dict[Language]}",\n'
110                '    "{description}",\n'
111                '    "{json_dict[RegistryPrefix]}",\n'
112                '    "{json_dict[OwningEntity]}",\n'
113                "}};\n"
114                "\n"
115                "static constexpr const char* url =\n"
116                '    "{url}";\n'
117                "\n"
118                "static constexpr std::array registry =\n"
119                "{{\n".format(
120                    json_dict=json_dict,
121                    url=url,
122                    version_split=version_split,
123                    description=description,
124                )
125            )
126
127            messages_sorted = sorted(json_dict["Messages"].items())
128            for messageId, message in messages_sorted:
129                registry.write(
130                    "    MessageEntry{{\n"
131                    '        "{messageId}",\n'
132                    "        {{\n"
133                    '            "{message[Description]}",\n'
134                    '            "{message[Message]}",\n'
135                    '            "{message[MessageSeverity]}",\n'
136                    "            {message[NumberOfArgs]},\n"
137                    "            {{".format(
138                        messageId=messageId, message=message
139                    )
140                )
141                paramTypes = message.get("ParamTypes")
142                if paramTypes:
143                    for paramType in paramTypes:
144                        registry.write(
145                            '\n                "{}",'.format(paramType)
146                        )
147                    registry.write("\n            },\n")
148                else:
149                    registry.write("},\n")
150                registry.write(
151                    '            "{message[Resolution]}",\n        }}}},\n'.format(
152                        message=message
153                    )
154                )
155
156            registry.write("\n};\n\nenum class Index\n{\n")
157            for index, (messageId, message) in enumerate(messages_sorted):
158                messageId = messageId[0].lower() + messageId[1:]
159                registry.write("    {} = {},\n".format(messageId, index))
160            registry.write("};\n")
161            registry.write("}}; // struct {}\n".format(namespace))
162            registry.write("\n")
163            registry.write(
164                "[[gnu::constructor]] inline void register{}()\n".format(
165                    to_pascal_case(namespace)
166                )
167            )
168            registry.write(
169                "{{ registerRegistry<{}>(); }}\n".format(
170                    to_pascal_case(namespace)
171                )
172            )
173            registry.write("\n")
174            registry.write("} // namespace redfish::registries\n")
175
176
177def get_privilege_string_from_list(
178    privilege_list: t.List[t.Dict[str, t.Any]],
179) -> str:
180    privilege_string = "{{\n"
181    for privilege_json in privilege_list:
182        privileges = privilege_json["Privilege"]
183        privilege_string += "    {"
184        last_privelege = ""
185        for privilege in privileges:
186            last_privelege = privilege
187            if privilege == "NoAuth":
188                continue
189            privilege_string += '"'
190            privilege_string += privilege
191            privilege_string += '",\n'
192        if last_privelege != "NoAuth":
193            privilege_string = privilege_string[:-2]
194        privilege_string += "}"
195        privilege_string += ",\n"
196    privilege_string = privilege_string[:-2]
197    privilege_string += "\n}}"
198    return privilege_string
199
200
201def get_variable_name_for_privilege_set(
202    privilege_list: t.List[t.Dict[str, t.Any]],
203) -> str:
204    names = []
205    for privilege_json in privilege_list:
206        privileges = privilege_json["Privilege"]
207        names.append("And".join(privileges))
208    return "Or".join(names)
209
210
211PRIVILEGE_HEADER = COPYRIGHT + PRAGMA_ONCE + WARNING + """
212#include "privileges.hpp"
213
214#include <array>
215
216// clang-format off
217
218namespace redfish::privileges
219{
220"""
221
222
223def get_response_code(entry_id: str) -> str | None:
224    codes = {
225        "AccessDenied": "forbidden",
226        "AccountForSessionNoLongerExists": "forbidden",
227        "AccountModified": "ok",
228        "AccountRemoved": "ok",
229        "CouldNotEstablishConnection": "not_found",
230        "Created": "created",
231        "EventSubscriptionLimitExceeded": "service_unavailable",
232        "GeneralError": "internal_server_error",
233        "GenerateSecretKeyRequired": "forbidden",
234        "InsufficientPrivilege": "forbidden",
235        "InsufficientStorage": "insufficient_storage",
236        "InternalError": "internal_server_error",
237        "MaximumErrorsExceeded": "internal_server_error",
238        "NoValidSession": "forbidden",
239        "OperationFailed": "bad_gateway",
240        "OperationNotAllowed": "method_not_allowed",
241        "OperationTimeout": "internal_server_error",
242        "PasswordChangeRequired": None,
243        "PreconditionFailed": "precondition_failed",
244        "PropertyNotWritable": "method_not_allowed",
245        "PropertyValueExternalConflict": "conflict",
246        "PropertyValueModified": "ok",
247        "PropertyValueResourceConflict": "conflict",
248        "ResourceAtUriUnauthorized": "unauthorized",
249        "ResourceCannotBeDeleted": "method_not_allowed",
250        "ResourceExhaustion": "service_unavailable",
251        "ResourceInStandby": "service_unavailable",
252        "ResourceInUse": "service_unavailable",
253        "ResourceNotFound": "not_found",
254        "RestrictedRole": "forbidden",
255        "ServiceDisabled": "service_unavailable",
256        "ServiceInUnknownState": "service_unavailable",
257        "ServiceShuttingDown": "service_unavailable",
258        "ServiceTemporarilyUnavailable": "service_unavailable",
259        "SessionLimitExceeded": "service_unavailable",
260        "SessionTerminated": "ok",
261        "SubscriptionTerminated": "ok",
262        "Success": None,
263    }
264
265    return codes.get(entry_id, "bad_request")
266
267
268def make_error_function(
269    entry_id: str,
270    entry: t.Dict[str, t.Any],
271    is_header: bool,
272    registry_name: str,
273    namespace_name: str,
274) -> str:
275    struct_name = to_pascal_case(namespace_name)
276    arg_nonstring_types = {
277        "const boost::urls::url_view_base&": {
278            "AccessDenied": [1],
279            "CouldNotEstablishConnection": [1],
280            "GenerateSecretKeyRequired": [1],
281            "InvalidObject": [1],
282            "PasswordChangeRequired": [1],
283            "PropertyValueResourceConflict": [3],
284            "ResetRequired": [1],
285            "ResourceAtUriInUnknownFormat": [1],
286            "ResourceAtUriUnauthorized": [1],
287            "ResourceCreationConflict": [1],
288            "ResourceMissingAtURI": [1],
289            "SourceDoesNotSupportProtocol": [1],
290        },
291        "const nlohmann::json&": {
292            "ActionParameterValueError": [1],
293            "ActionParameterValueFormatError": [1],
294            "ActionParameterValueTypeError": [1],
295            "PropertyValueExternalConflict": [2],
296            "PropertyValueFormatError": [1],
297            "PropertyValueIncorrect": [2],
298            "PropertyValueModified": [2],
299            "PropertyValueNotInList": [1],
300            "PropertyValueOutOfRange": [1],
301            "PropertyValueResourceConflict": [2],
302            "PropertyValueTypeError": [1],
303            "QueryParameterValueFormatError": [1],
304            "QueryParameterValueTypeError": [1],
305        },
306    }
307
308    out = ""
309    args = []
310    argtypes = []
311    for arg_index, arg in enumerate(entry.get("ParamTypes", [])):
312        arg_index += 1
313        typename = "std::string_view"
314        if arg == "number":
315            typename = "uint64_t"
316        for typestring, entries in arg_nonstring_types.items():
317            if arg_index in entries.get(entry_id, []):
318                typename = typestring
319
320        argtypes.append(typename)
321        args.append(f"{typename} arg{arg_index}")
322    function_name = entry_id[0].lower() + entry_id[1:]
323    arg = ", ".join(args)
324    out += f"nlohmann::json::object_t {function_name}({arg})"
325
326    if is_header:
327        out += ";\n\n"
328    else:
329        out += "\n{\n"
330        to_array_type = ""
331        arg_param = "{}"
332        if argtypes:
333            outargs = []
334            for index, typename in enumerate(argtypes):
335                index += 1
336                if typename == "const nlohmann::json&":
337                    out += f"std::string arg{index}Str = arg{index}.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);\n"
338                elif typename == "uint64_t":
339                    out += f"std::string arg{index}Str = std::to_string(arg{index});\n"
340
341            for index, typename in enumerate(argtypes):
342                index += 1
343                if typename == "const boost::urls::url_view_base&":
344                    outargs.append(f"arg{index}.buffer()")
345                    to_array_type = "<std::string_view>"
346                elif typename == "const nlohmann::json&":
347                    outargs.append(f"arg{index}Str")
348                    to_array_type = "<std::string_view>"
349                elif typename == "uint64_t":
350                    outargs.append(f"arg{index}Str")
351                    to_array_type = "<std::string_view>"
352                else:
353                    outargs.append(f"arg{index}")
354            argstring = ", ".join(outargs)
355            arg_param = f"std::to_array{to_array_type}({{{argstring}}})"
356        out += f"    return getLog(redfish::registries::{struct_name}::Index::{function_name}, {arg_param});"
357        out += "\n}\n\n"
358    if registry_name == "Base":
359        args.insert(0, "crow::Response& res")
360        if entry_id == "InternalError":
361            if is_header:
362                args.append(
363                    "std::source_location location = std::source_location::current()"
364                )
365            else:
366                args.append("const std::source_location location")
367        arg = ", ".join(args)
368        out += f"void {function_name}({arg})"
369        if is_header:
370            out += ";\n"
371        else:
372            out += "\n{\n"
373            if entry_id == "InternalError":
374                out += """BMCWEB_LOG_CRITICAL("Internal Error {}({}:{}) `{}`: ", location.file_name(),
375                            location.line(), location.column(),
376                            location.function_name());\n"""
377
378            if entry_id == "ServiceTemporarilyUnavailable":
379                out += "res.addHeader(boost::beast::http::field::retry_after, arg1);"
380
381            res = get_response_code(entry_id)
382            if res:
383                out += f"    res.result(boost::beast::http::status::{res});\n"
384            args_out = ", ".join([f"arg{x + 1}" for x in range(len(argtypes))])
385
386            addMessageToJson = {
387                "PropertyDuplicate": 1,
388                "ResourceAlreadyExists": 2,
389                "CreateFailedMissingReqProperties": 1,
390                "PropertyValueFormatError": 2,
391                "PropertyValueNotInList": 2,
392                "PropertyValueTypeError": 2,
393                "PropertyValueError": 1,
394                "PropertyNotWritable": 1,
395                "PropertyValueModified": 1,
396                "PropertyMissing": 1,
397            }
398
399            addMessageToRoot = [
400                "SessionTerminated",
401                "SubscriptionTerminated",
402                "AccountRemoved",
403                "Created",
404                "Success",
405            ]
406
407            if entry_id in addMessageToJson:
408                out += f"    addMessageToJson(res.jsonValue, {function_name}({args_out}), arg{addMessageToJson[entry_id]});\n"
409            elif entry_id in addMessageToRoot:
410                out += f"    addMessageToJsonRoot(res.jsonValue, {function_name}({args_out}));\n"
411            else:
412                out += f"    addMessageToErrorJson(res.jsonValue, {function_name}({args_out}));\n"
413            out += "}\n"
414    out += "\n"
415    return out
416
417
418def create_error_registry(
419    registry_info: RegistryInfo,
420    registry_name: str,
421    namespace_name: str,
422    filename: str,
423) -> None:
424    file, json_dict, namespace, url = registry_info
425    base_filename = filename + "_messages"
426    struct_name = to_pascal_case(namespace_name)
427
428    error_messages_hpp = os.path.join(
429        SCRIPT_DIR, "..", "redfish-core", "include", f"{base_filename}.hpp"
430    )
431    messages = json_dict["Messages"]
432
433    with open(
434        error_messages_hpp,
435        "w",
436    ) as out:
437        out.write(PRAGMA_ONCE)
438        out.write(WARNING)
439        out.write("""
440// These generated headers are a superset of what is needed.
441// clang sees them as an error, so ignore
442// NOLINTBEGIN(misc-include-cleaner)
443#include "http_response.hpp"
444
445#include <boost/url/url_view_base.hpp>
446#include <nlohmann/json.hpp>
447
448#include <cstdint>
449#include <source_location>
450#include <string_view>
451// NOLINTEND(misc-include-cleaner)
452
453namespace redfish
454{
455
456namespace messages
457{
458""")
459        for entry_id, entry in messages.items():
460            message = entry["Message"]
461            for index in range(1, 10):
462                message = message.replace(f"'%{index}'", f"<arg{index}>")
463                message = message.replace(f"%{index}", f"<arg{index}>")
464
465            if registry_name == "Base":
466                out.write("/**\n")
467                out.write(f"* @brief Formats {entry_id} message into JSON\n")
468                out.write(f'* Message body: "{message}"\n')
469                out.write("*\n")
470                arg_index = 0
471                for arg_index, arg in enumerate(entry.get("ParamTypes", [])):
472                    arg_index += 1
473
474                    out.write(
475                        f"* @param[in] arg{arg_index} Parameter of message that will replace %{arg_index} in its body.\n"
476                    )
477                out.write("*\n")
478                out.write(
479                    f"* @returns Message {entry_id} formatted to JSON */\n"
480                )
481
482            out.write(
483                make_error_function(
484                    entry_id, entry, True, registry_name, namespace_name
485                )
486            )
487        out.write("    }\n")
488        out.write("}\n")
489
490    error_messages_cpp = os.path.join(
491        SCRIPT_DIR, "..", "redfish-core", "src", f"{base_filename}.cpp"
492    )
493    with open(
494        error_messages_cpp,
495        "w",
496    ) as out:
497        out.write(WARNING)
498        out.write(f'\n#include "{base_filename}.hpp"\n')
499        headers = []
500
501        headers.append('"registries.hpp"')
502        if registry_name == "Base":
503            reg_name_lower = "base"
504            headers.append('"error_message_utils.hpp"')
505            headers.append('"http_response.hpp"')
506            headers.append('"logging.hpp"')
507            headers.append("<boost/beast/http/field.hpp>")
508            headers.append("<boost/beast/http/status.hpp>")
509            headers.append("<boost/url/url_view_base.hpp>")
510            headers.append("<source_location>")
511        else:
512            reg_name_lower = namespace_name.lower()
513        headers.append(f'"registries/{reg_name_lower}_message_registry.hpp"')
514
515        headers.append("<nlohmann/json.hpp>")
516        headers.append("<array>")
517        headers.append("<cstddef>")
518        headers.append("<span>")
519        headers.append("<string_view>")
520
521        for header in headers:
522            out.write(f"#include {header}\n")
523
524        out.write("""
525// Clang can't seem to decide whether this header needs to be included or not,
526// and is inconsistent.  Include it for now
527// NOLINTNEXTLINE(misc-include-cleaner)
528#include <cstdint>
529// NOLINTNEXTLINE(misc-include-cleaner)
530#include <string>
531// NOLINTNEXTLINE(misc-include-cleaner)
532#include <utility>
533
534namespace redfish
535{
536
537namespace messages
538{
539""")
540        out.write("""
541static nlohmann::json::object_t getLog(redfish::registries::{struct_name}::Index name,
542                             std::span<const std::string_view> args)
543{{
544    size_t index = static_cast<size_t>(name);
545    if (index >= redfish::registries::{struct_name}::registry.size())
546    {{
547        return {{}};
548    }}
549    return getLogFromRegistry(redfish::registries::{struct_name}::header,
550                              redfish::registries::{struct_name}::registry, index, args);
551}}
552
553""".format(struct_name=struct_name))
554        for entry_id, entry in messages.items():
555            out.write(f"""/**
556 * @internal
557 * @brief Formats {entry_id} message into JSON
558 *
559 * See header file for more information
560 * @endinternal
561 */
562""")
563            message = entry["Message"]
564            out.write(
565                make_error_function(
566                    entry_id, entry, False, registry_name, namespace_name
567                )
568            )
569
570        out.write("    }\n")
571        out.write("}\n")
572    os.system(f"clang-format -i {error_messages_hpp} {error_messages_cpp}")
573
574
575def make_privilege_registry() -> None:
576    path, json_file, type_name, url = make_getter(
577        "Redfish_1.5.0_PrivilegeRegistry.json",
578        "privilege_registry.hpp",
579        "privilege",
580    )
581    with open(path, "w") as registry:
582        registry.write(PRIVILEGE_HEADER)
583
584        privilege_dict: t.Dict[str, t.Tuple[t.Any, str | None]] = {}
585        for mapping in json_file["Mappings"]:
586            # first pass, identify all the unique privilege sets
587            for operation, privilege_list in mapping["OperationMap"].items():
588                privilege_dict[
589                    get_privilege_string_from_list(privilege_list)
590                ] = (
591                    privilege_list,
592                    None,
593                )
594            if "SubordinateOverrides" in mapping:
595                for subordinateOverride in mapping["SubordinateOverrides"]:
596                    for operation, privilege_list in subordinateOverride[
597                        "OperationMap"
598                    ].items():
599                        privilege_dict[
600                            get_privilege_string_from_list(privilege_list)
601                        ] = (
602                            privilege_list,
603                            None,
604                        )
605        for index, key in enumerate(privilege_dict):
606            privilege_list, _ = privilege_dict[key]
607            name = get_variable_name_for_privilege_set(privilege_list)
608            registry.write(
609                "const std::array<Privileges, {length}> "
610                "privilegeSet{name} = {key};\n".format(
611                    length=len(privilege_list), name=name, key=key
612                )
613            )
614            privilege_dict[key] = (privilege_list, name)
615
616        for mapping in json_file["Mappings"]:
617            entity = mapping["Entity"]
618            registry.write("// {}\n".format(entity))
619            for operation, privilege_list in mapping["OperationMap"].items():
620                privilege_string = get_privilege_string_from_list(
621                    privilege_list
622                )
623                operation = operation.lower()
624
625                registry.write(
626                    "const static auto& {}{} = privilegeSet{};\n".format(
627                        operation, entity, privilege_dict[privilege_string][1]
628                    )
629                )
630            registry.write("\n")
631            if "SubordinateOverrides" in mapping:
632                for subordinateOverrides in mapping["SubordinateOverrides"]:
633                    target_list_list = subordinateOverrides["Targets"]
634                    registry.write("// Subordinate override for ")
635                    concateVarName = ""
636                    for target in target_list_list:
637                        registry.write(target + " -> ")
638                        concateVarName += target
639                    registry.write(entity)
640                    registry.write("\n")
641                    for operation, privilege_list in subordinateOverrides[
642                        "OperationMap"
643                    ].items():
644                        privilege_string = get_privilege_string_from_list(
645                            privilege_list
646                        )
647                        operation = operation.lower()
648                        registry.write(
649                            "const static auto& {}{}SubOver{} = privilegeSet{};\n".format(
650                                operation,
651                                entity,
652                                concateVarName,
653                                privilege_dict[privilege_string][1],
654                            )
655                        )
656                    registry.write("\n")
657        registry.write(
658            "} // namespace redfish::privileges\n// clang-format on\n"
659        )
660
661
662def to_pascal_case(text: str) -> str:
663    s = text.replace("_", " ")
664    s1 = s.split()
665    if len(text) == 0:
666        return text
667    return "".join(i.capitalize() for i in s1[0:])
668
669
670def main() -> None:
671    dmtf_registries = OrderedDict(
672        [
673            ("base", "1.19.0"),
674            ("composition", "1.1.2"),
675            ("environmental", "1.1.0"),
676            ("ethernet_fabric", "1.0.1"),
677            ("fabric", "1.0.2"),
678            ("heartbeat_event", "1.0.1"),
679            ("job_event", "1.0.1"),
680            ("license", "1.0.3"),
681            ("log_service", "1.0.1"),
682            ("network_device", "1.0.3"),
683            ("platform", "1.0.1"),
684            ("power", "1.0.1"),
685            ("resource_event", "1.3.0"),
686            ("sensor_event", "1.0.1"),
687            ("storage_device", "1.2.1"),
688            ("task_event", "1.0.3"),
689            ("telemetry", "1.0.0"),
690            ("update", "1.2.0"),
691        ]
692    )
693
694    parser = argparse.ArgumentParser()
695    parser.add_argument(
696        "--registries",
697        type=str,
698        default="privilege,openbmc,"
699        + ",".join([dmtf for dmtf in dmtf_registries]),
700        help="Comma delimited list of registries to update",
701    )
702
703    args = parser.parse_args()
704
705    registries = set(args.registries.split(","))
706    registries_map: t.OrderedDict[str, RegistryInfo] = OrderedDict()
707
708    for registry, version in dmtf_registries.items():
709        if registry in registries:
710            registry_pascal_case = to_pascal_case(registry)
711            registries_map[registry] = make_getter(
712                f"{registry_pascal_case}.{version}.json",
713                f"{registry}_message_registry.hpp",
714                registry,
715            )
716    if "openbmc" in registries:
717        registries_map["openbmc"] = openbmc_local_getter()
718
719    update_registries(list(registries_map.values()))
720
721    if "base" in registries_map:
722        create_error_registry(
723            registries_map["base"],
724            "Base",
725            "base",
726            "error",
727        )
728    if "heartbeat_event" in registries_map:
729        create_error_registry(
730            registries_map["heartbeat_event"],
731            "HeartbeatEvent",
732            "heartbeat_event",
733            "heartbeat",
734        )
735    if "resource_event" in registries_map:
736        create_error_registry(
737            registries_map["resource_event"],
738            "ResourceEvent",
739            "resource_event",
740            "resource",
741        )
742    if "task_event" in registries_map:
743        create_error_registry(
744            registries_map["task_event"],
745            "TaskEvent",
746            "task_event",
747            "task",
748        )
749    if "update" in registries_map:
750        create_error_registry(
751            registries_map["update"],
752            "Update",
753            "update",
754            "update",
755        )
756
757    if "privilege" in registries:
758        make_privilege_registry()
759
760
761if __name__ == "__main__":
762    main()
763