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