2023-03-04 14:08:15 +01:00
|
|
|
def conf_schema_active(
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
return {
|
2022-12-05 08:16:41 +01:00
|
|
|
"description": "whether the check shall be executed",
|
2022-12-03 16:36:44 +01:00
|
|
|
"type": "boolean",
|
2022-12-05 08:16:41 +01:00
|
|
|
"default": True,
|
2022-12-03 16:36:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_schema_threshold(
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
return {
|
|
|
|
|
"description": "how often a condition has to occur in order to be reported",
|
|
|
|
|
"type": "integer",
|
|
|
|
|
"minimum": 1,
|
2022-12-05 08:16:41 +01:00
|
|
|
"default": 3,
|
2022-12-03 16:36:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_schema_annoy(
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
return {
|
2023-03-05 15:52:28 +01:00
|
|
|
"description": "whether notifications about non-ok states shall be kept sending after the threshold has been surpassed",
|
2022-12-03 16:36:44 +01:00
|
|
|
"type": "boolean",
|
2022-12-05 08:16:41 +01:00
|
|
|
"default": False,
|
2022-12-03 16:36:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_schema_interval(
|
2023-03-22 11:40:28 +01:00
|
|
|
allow_null,
|
2023-03-04 14:08:15 +01:00
|
|
|
default
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
return {
|
|
|
|
|
"anyOf": [
|
|
|
|
|
{
|
2022-12-05 08:16:41 +01:00
|
|
|
"description": "in seconds",
|
2023-03-22 11:40:28 +01:00
|
|
|
"type": ("integer" if allow_null else ["null", "integer"]),
|
2022-12-05 08:16:41 +01:00
|
|
|
"exclusiveMinimum": 0,
|
2022-12-03 16:36:44 +01:00
|
|
|
},
|
|
|
|
|
{
|
2022-12-05 08:16:41 +01:00
|
|
|
"description": "as text",
|
2022-12-03 16:36:44 +01:00
|
|
|
"type": "string",
|
|
|
|
|
"enum": [
|
|
|
|
|
"minute",
|
|
|
|
|
"hour",
|
|
|
|
|
"day",
|
|
|
|
|
"week",
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
"default": default,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_schema_schedule(
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
return {
|
|
|
|
|
"type": "object",
|
|
|
|
|
"additionalProperties": False,
|
|
|
|
|
"properties": {
|
2023-03-22 11:40:28 +01:00
|
|
|
"regular_interval": conf_schema_interval(False, (60 * 60)),
|
|
|
|
|
"attentive_interval": conf_schema_interval(False, (60 * 2)),
|
|
|
|
|
"reminding_interval": conf_schema_interval(True, (60 * 60 * 24)),
|
2022-12-03 16:36:44 +01:00
|
|
|
},
|
|
|
|
|
"required": [
|
2022-12-05 08:16:41 +01:00
|
|
|
],
|
2022-12-03 16:36:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_schema_notifications(
|
|
|
|
|
notification_channel_implementations
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
return {
|
|
|
|
|
"type": "array",
|
|
|
|
|
"items": {
|
|
|
|
|
"anyOf": list(
|
|
|
|
|
map(
|
|
|
|
|
lambda pair: {
|
2022-12-15 15:48:52 +01:00
|
|
|
"title": ("notification channel '%s'" % pair[0]),
|
2022-12-03 16:36:44 +01:00
|
|
|
"type": "object",
|
|
|
|
|
"unevaluatedProperties": False,
|
|
|
|
|
"properties": {
|
|
|
|
|
"kind": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"enum": [pair[0]]
|
|
|
|
|
},
|
|
|
|
|
"parameters": pair[1].parameters_schema(),
|
|
|
|
|
},
|
|
|
|
|
"required": [
|
|
|
|
|
"kind",
|
|
|
|
|
"parameters"
|
2022-12-05 08:16:41 +01:00
|
|
|
],
|
2022-12-03 16:36:44 +01:00
|
|
|
},
|
|
|
|
|
notification_channel_implementations.items()
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
"default": [
|
|
|
|
|
{
|
|
|
|
|
"kind": "console",
|
|
|
|
|
"parameters": {
|
|
|
|
|
}
|
2022-12-05 08:16:41 +01:00
|
|
|
},
|
|
|
|
|
],
|
2022-12-03 16:36:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_schema_root(
|
|
|
|
|
check_kind_implementations,
|
|
|
|
|
notification_channel_implementations
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
return {
|
|
|
|
|
"type": "object",
|
|
|
|
|
"additionalProperties": False,
|
|
|
|
|
"properties": {
|
|
|
|
|
"defaults": {
|
|
|
|
|
"description": "default values for checks",
|
|
|
|
|
"type": "object",
|
|
|
|
|
"additionalProperties": False,
|
|
|
|
|
"properties": {
|
|
|
|
|
"active": conf_schema_active(),
|
|
|
|
|
"threshold": conf_schema_threshold(),
|
|
|
|
|
"annoy": conf_schema_annoy(),
|
|
|
|
|
"schedule": conf_schema_schedule(),
|
|
|
|
|
"notifications": conf_schema_notifications(notification_channel_implementations),
|
|
|
|
|
},
|
|
|
|
|
"required": [
|
2022-12-05 08:16:41 +01:00
|
|
|
],
|
2022-12-03 16:36:44 +01:00
|
|
|
},
|
2023-03-03 18:50:21 +01:00
|
|
|
"includes": {
|
|
|
|
|
"type": "array",
|
|
|
|
|
"items": {
|
|
|
|
|
"type": "string"
|
|
|
|
|
},
|
|
|
|
|
"default": [],
|
|
|
|
|
"description": "list of relative or absolute paths to other hmdl files on the local machine, which shall be subsumed in the overall monitoring task"
|
|
|
|
|
},
|
2022-12-03 16:36:44 +01:00
|
|
|
"checks": {
|
|
|
|
|
"type": "array",
|
|
|
|
|
"items": {
|
|
|
|
|
"allOf": [
|
|
|
|
|
{
|
|
|
|
|
"description": "should represent a specific check",
|
|
|
|
|
"type": "object",
|
|
|
|
|
"unevaluatedProperties": False,
|
|
|
|
|
"properties": {
|
|
|
|
|
"name": {
|
|
|
|
|
"type": "string"
|
|
|
|
|
},
|
|
|
|
|
"title": {
|
|
|
|
|
"type": "string"
|
|
|
|
|
},
|
|
|
|
|
"active": conf_schema_active(),
|
|
|
|
|
"threshold": conf_schema_threshold(),
|
|
|
|
|
"annoy": conf_schema_annoy(),
|
|
|
|
|
"schedule": conf_schema_schedule(),
|
|
|
|
|
"notifications": conf_schema_notifications(notification_channel_implementations),
|
|
|
|
|
},
|
|
|
|
|
"required": [
|
|
|
|
|
"name",
|
2022-12-05 08:16:41 +01:00
|
|
|
],
|
2022-12-03 16:36:44 +01:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"anyOf": list(
|
|
|
|
|
map(
|
|
|
|
|
lambda pair: {
|
2023-02-06 16:06:18 +01:00
|
|
|
"title": ("check '%s'" % pair[0]),
|
2022-12-03 16:36:44 +01:00
|
|
|
"type": "object",
|
|
|
|
|
"unevaluatedProperties": False,
|
|
|
|
|
"properties": {
|
|
|
|
|
"kind": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"enum": [pair[0]]
|
|
|
|
|
},
|
|
|
|
|
"parameters": pair[1].parameters_schema(),
|
2023-04-26 17:27:47 +02:00
|
|
|
"custom": {
|
|
|
|
|
"description": "custom data, which shall be attached to notifications",
|
|
|
|
|
"default": None,
|
|
|
|
|
},
|
2022-12-03 16:36:44 +01:00
|
|
|
},
|
|
|
|
|
"required": [
|
|
|
|
|
"kind",
|
2022-12-05 08:16:41 +01:00
|
|
|
"parameters",
|
2022-12-03 16:36:44 +01:00
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
check_kind_implementations.items()
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"required": [
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_normalize_interval(
|
|
|
|
|
interval_raw
|
|
|
|
|
):
|
2023-03-22 11:40:28 +01:00
|
|
|
if (interval_raw is None):
|
|
|
|
|
return None
|
2022-12-03 16:36:44 +01:00
|
|
|
else:
|
2023-03-22 11:40:28 +01:00
|
|
|
if (type(interval_raw) == int):
|
|
|
|
|
return interval_raw
|
|
|
|
|
elif (type(interval_raw) == str):
|
|
|
|
|
map_ = {
|
|
|
|
|
"minute": (60),
|
|
|
|
|
"hour": (60 * 60),
|
|
|
|
|
"day": (60 * 60 * 24),
|
|
|
|
|
"week": (60 * 60 * 24 * 7),
|
|
|
|
|
}
|
|
|
|
|
if (interval_raw not in map_):
|
|
|
|
|
raise ValueError("invalid string interval value: %s" % interval_raw)
|
|
|
|
|
else:
|
|
|
|
|
return map_[interval_raw]
|
|
|
|
|
else:
|
|
|
|
|
raise ValueError("invalid type for interval value")
|
|
|
|
|
|
2022-12-03 16:36:44 +01:00
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_normalize_schedule(
|
|
|
|
|
node
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
node_ = dict_merge(
|
|
|
|
|
{
|
|
|
|
|
"regular_interval": (60 * 60),
|
|
|
|
|
"attentive_interval": (60 * 2),
|
2023-03-22 11:40:28 +01:00
|
|
|
"reminding_interval": (60 * 60 * 24),
|
2022-12-03 16:36:44 +01:00
|
|
|
},
|
|
|
|
|
node
|
|
|
|
|
)
|
|
|
|
|
return {
|
2023-03-22 14:29:06 +01:00
|
|
|
"regular_interval": conf_normalize_interval(node_["regular_interval"]),
|
|
|
|
|
"attentive_interval": conf_normalize_interval(node_["attentive_interval"]),
|
|
|
|
|
"reminding_interval": conf_normalize_interval(node_["reminding_interval"]),
|
2022-12-03 16:36:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_normalize_notification(
|
|
|
|
|
notification_channel_implementations,
|
|
|
|
|
node
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
if (node["kind"] not in notification_channel_implementations):
|
|
|
|
|
raise ValueError("invalid notification kind: %s" % notification["kind"])
|
|
|
|
|
else:
|
|
|
|
|
return {
|
|
|
|
|
"kind": node["kind"],
|
|
|
|
|
"parameters": notification_channel_implementations[node["kind"]].normalize_conf_node(node["parameters"]),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_normalize_defaults(
|
|
|
|
|
notification_channel_implementations,
|
|
|
|
|
node
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
node_ = dict_merge(
|
|
|
|
|
{
|
|
|
|
|
"active": True,
|
|
|
|
|
"threshold": 3,
|
|
|
|
|
"annoy": False,
|
|
|
|
|
"schedule": {
|
|
|
|
|
"regular_interval": (60 * 60),
|
|
|
|
|
"attentive_interval": (60 * 2),
|
2023-03-22 11:40:28 +01:00
|
|
|
"reminding_interval": (60 * 60 * 24),
|
2022-12-03 16:36:44 +01:00
|
|
|
},
|
|
|
|
|
"notifications": [
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
node
|
|
|
|
|
)
|
|
|
|
|
return {
|
|
|
|
|
"active": node_["active"],
|
|
|
|
|
"threshold": node_["threshold"],
|
|
|
|
|
"annoy": node_["annoy"],
|
|
|
|
|
"schedule": node_["schedule"],
|
|
|
|
|
"notifications": list(
|
|
|
|
|
map(
|
|
|
|
|
lambda x: conf_normalize_notification(notification_channel_implementations, x),
|
|
|
|
|
node_["notifications"]
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-04 14:08:15 +01:00
|
|
|
def conf_normalize_check(
|
|
|
|
|
check_kind_implementations,
|
|
|
|
|
notification_channel_implementations,
|
|
|
|
|
defaults,
|
|
|
|
|
node
|
|
|
|
|
):
|
2022-12-03 16:36:44 +01:00
|
|
|
if ("name" not in node):
|
|
|
|
|
raise ValueError("missing mandatory field in 'check' node: 'name'")
|
|
|
|
|
else:
|
|
|
|
|
if ("kind" not in node):
|
|
|
|
|
raise ValueError("missing mandatory field in 'check' node: 'kind'")
|
|
|
|
|
else:
|
|
|
|
|
if (node["kind"] not in check_kind_implementations):
|
|
|
|
|
raise ValueError("invalid check kind: '%s'" % node["kind"])
|
|
|
|
|
else:
|
|
|
|
|
node_ = dict_merge(
|
2023-03-04 14:08:15 +01:00
|
|
|
dict_merge(
|
|
|
|
|
defaults,
|
|
|
|
|
{
|
|
|
|
|
"title": node["name"],
|
|
|
|
|
"parameters": {},
|
2023-04-26 17:27:47 +02:00
|
|
|
"custom": None,
|
2023-03-04 14:08:15 +01:00
|
|
|
},
|
|
|
|
|
),
|
2022-12-03 16:36:44 +01:00
|
|
|
node
|
|
|
|
|
)
|
2023-03-04 14:08:15 +01:00
|
|
|
node__ = {}
|
|
|
|
|
if True:
|
|
|
|
|
node__["name"] = node_["name"]
|
|
|
|
|
if True:
|
|
|
|
|
node__["title"] = node_["title"]
|
|
|
|
|
if ("active" in node_):
|
|
|
|
|
node__["active"] = node_["active"]
|
|
|
|
|
if ("threshold" in node_):
|
|
|
|
|
node__["threshold"] = node_["threshold"]
|
|
|
|
|
if ("annoy" in node_):
|
|
|
|
|
node__["annoy"] = node_["annoy"]
|
|
|
|
|
if ("schedule" in node_):
|
|
|
|
|
node__["schedule"] = conf_normalize_schedule(node_["schedule"])
|
2023-03-04 15:20:35 +01:00
|
|
|
if ("notifications" in node_):
|
|
|
|
|
node__["notifications"] = list(
|
2023-03-04 14:08:15 +01:00
|
|
|
map(
|
|
|
|
|
lambda x: conf_normalize_notification(notification_channel_implementations, x),
|
|
|
|
|
node_["notifications"]
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
if ("kind" in node_):
|
|
|
|
|
node__["kind"] = node_["kind"]
|
|
|
|
|
if True:
|
|
|
|
|
node__["parameters"] = check_kind_implementations[node_["kind"]].normalize_conf_node(node_["parameters"])
|
2023-04-26 17:27:47 +02:00
|
|
|
if ("custom" in node_):
|
|
|
|
|
node__["custom"] = node_["custom"]
|
2023-03-04 14:08:15 +01:00
|
|
|
return node__
|
2022-12-03 16:36:44 +01:00
|
|
|
|
|
|
|
|
|
2023-03-03 18:50:21 +01:00
|
|
|
def conf_normalize_root(
|
|
|
|
|
check_kind_implementations,
|
|
|
|
|
notification_channel_implementations,
|
2023-03-04 14:08:15 +01:00
|
|
|
node,
|
|
|
|
|
options = None
|
2023-03-03 18:50:21 +01:00
|
|
|
):
|
2023-03-04 14:08:15 +01:00
|
|
|
options = dict_merge(
|
|
|
|
|
{
|
|
|
|
|
"use_implicit_default_values": True,
|
|
|
|
|
},
|
|
|
|
|
({} if (options is None) else options)
|
|
|
|
|
)
|
2022-12-03 16:36:44 +01:00
|
|
|
counts = {}
|
2023-03-03 18:50:21 +01:00
|
|
|
checks_raw = (
|
|
|
|
|
node["checks"]
|
|
|
|
|
if ("checks" in node) else
|
|
|
|
|
[]
|
|
|
|
|
)
|
|
|
|
|
for node_ in checks_raw:
|
2022-12-03 16:36:44 +01:00
|
|
|
if (node_["name"] not in counts):
|
|
|
|
|
counts[node_["name"]] = 0
|
|
|
|
|
counts[node_["name"]] += 1
|
|
|
|
|
fails = list(filter(lambda pair: (pair[1] > 1), counts.items()))
|
|
|
|
|
if (len(fails) > 0):
|
|
|
|
|
raise ValueError(
|
|
|
|
|
string_coin(
|
|
|
|
|
"ambiguous check names: {{names}}",
|
|
|
|
|
{
|
2023-04-26 17:27:47 +02:00
|
|
|
"names": ",".join(map(lambda pair: pair[0], fails)),
|
2022-12-03 16:36:44 +01:00
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else:
|
2023-03-04 14:08:15 +01:00
|
|
|
defaults_raw = (
|
|
|
|
|
node["defaults"]
|
|
|
|
|
if ("defaults" in node) else
|
|
|
|
|
{}
|
|
|
|
|
)
|
|
|
|
|
defaults = (
|
|
|
|
|
conf_normalize_defaults(
|
|
|
|
|
notification_channel_implementations,
|
|
|
|
|
defaults_raw
|
2023-03-03 18:50:21 +01:00
|
|
|
)
|
2023-03-04 14:08:15 +01:00
|
|
|
if options["use_implicit_default_values"] else
|
|
|
|
|
defaults_raw
|
2023-03-03 18:50:21 +01:00
|
|
|
)
|
|
|
|
|
includes = (
|
|
|
|
|
node["includes"]
|
|
|
|
|
if ("includes" in node) else
|
|
|
|
|
[]
|
|
|
|
|
)
|
2022-12-03 16:36:44 +01:00
|
|
|
return {
|
|
|
|
|
"defaults": defaults,
|
2023-03-03 18:50:21 +01:00
|
|
|
"includes": includes,
|
2022-12-03 16:36:44 +01:00
|
|
|
"checks": list(
|
|
|
|
|
map(
|
|
|
|
|
lambda node_: conf_normalize_check(
|
|
|
|
|
check_kind_implementations,
|
|
|
|
|
notification_channel_implementations,
|
|
|
|
|
defaults,
|
|
|
|
|
node_
|
|
|
|
|
),
|
2023-03-03 18:50:21 +01:00
|
|
|
checks_raw
|
2022-12-03 16:36:44 +01:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 18:50:21 +01:00
|
|
|
|
|
|
|
|
def conf_load(
|
|
|
|
|
check_kind_implementations,
|
|
|
|
|
notification_channel_implementations,
|
|
|
|
|
path,
|
2023-03-04 14:08:15 +01:00
|
|
|
options = None
|
2023-03-03 18:50:21 +01:00
|
|
|
):
|
2023-03-04 14:08:15 +01:00
|
|
|
options = dict_merge(
|
|
|
|
|
{
|
|
|
|
|
"root": True,
|
|
|
|
|
"already_included": set([]),
|
|
|
|
|
},
|
|
|
|
|
({} if (options is None) else options)
|
|
|
|
|
)
|
|
|
|
|
if (path in options["already_included"]):
|
2023-03-03 18:50:21 +01:00
|
|
|
raise ValueError("circular dependency detected")
|
|
|
|
|
else:
|
|
|
|
|
conf_raw = _json.loads(file_read(path))
|
|
|
|
|
includes = (
|
|
|
|
|
conf_raw["includes"]
|
|
|
|
|
if ("includes" in conf_raw) else
|
|
|
|
|
[]
|
|
|
|
|
)
|
|
|
|
|
for index in range(len(includes)):
|
|
|
|
|
path_ = includes[index]
|
|
|
|
|
sub_conf = conf_load(
|
|
|
|
|
check_kind_implementations,
|
|
|
|
|
notification_channel_implementations,
|
|
|
|
|
(
|
|
|
|
|
path_
|
|
|
|
|
if _os.path.isabs(path_) else
|
|
|
|
|
_os.path.join(_os.path.dirname(path), path_)
|
|
|
|
|
),
|
2023-03-04 14:08:15 +01:00
|
|
|
{
|
|
|
|
|
"root": False,
|
|
|
|
|
"already_included": (options["already_included"] | {path})
|
|
|
|
|
}
|
2023-03-03 18:50:21 +01:00
|
|
|
)
|
|
|
|
|
if (not "checks" in conf_raw):
|
|
|
|
|
conf_raw["checks"] = []
|
|
|
|
|
conf_raw["checks"].extend(
|
|
|
|
|
list(
|
|
|
|
|
map(
|
|
|
|
|
lambda check: dict_merge(
|
|
|
|
|
check,
|
|
|
|
|
{
|
|
|
|
|
"name": string_coin(
|
2023-03-04 15:29:53 +01:00
|
|
|
"{{prefix}}.{{original_name}}",
|
2023-03-03 18:50:21 +01:00
|
|
|
{
|
2023-03-04 15:29:53 +01:00
|
|
|
"prefix": _os.path.basename(path_).split(".")[0],
|
2023-03-03 18:50:21 +01:00
|
|
|
"original_name": check["name"],
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
sub_conf["checks"]
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
conf_raw["includes"] = []
|
|
|
|
|
return conf_normalize_root(
|
|
|
|
|
check_kind_implementations,
|
|
|
|
|
notification_channel_implementations,
|
2023-03-04 14:08:15 +01:00
|
|
|
conf_raw,
|
|
|
|
|
{
|
|
|
|
|
"use_implicit_default_values": options["root"],
|
|
|
|
|
}
|
2023-03-03 18:50:21 +01:00
|
|
|
)
|
|
|
|
|
|