[mod] JSON-schema für conf von Programm erzeugen lassen

This commit is contained in:
Christian Fraß 2022-11-30 23:03:24 +01:00
parent 453171c7f0
commit 09f043ef8d
14 changed files with 1318 additions and 642 deletions

665
doc/hmdl.schema.json Normal file
View file

@ -0,0 +1,665 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"defaults": {
"description": "default values for checks",
"type": "object",
"additionalProperties": false,
"properties": {
"active": {
"type": "boolean",
"default": true
},
"threshold": {
"description": "how often a condition has to occur in order to be reported",
"type": "integer",
"minimum": 1,
"default": 3
},
"annoy": {
"description": "whether notifications shall be kept sending after the threshold has been surpassed",
"type": "boolean",
"default": false
},
"schedule": {
"type": "object",
"additionalProperties": false,
"properties": {
"regular_interval": {
"description": "in seconds or as text",
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0
},
{
"type": "string",
"enum": [
"minute",
"hour",
"day",
"week"
]
}
],
"default": 3600
},
"attentive_interval": {
"description": "in seconds or as text",
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0
},
{
"type": "string",
"enum": [
"minute",
"hour",
"day",
"week"
]
}
],
"default": 120
}
},
"required": []
},
"notifications": {
"type": "array",
"items": {
"anyOf": [
{
"title": "check kind 'console'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"console"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {},
"required": []
}
},
"required": [
"kind",
"parameters"
]
},
{
"title": "check kind 'file_touch'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"file_touch"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
]
}
},
"required": [
"kind",
"parameters"
]
},
{
"title": "check kind 'email'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"email"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"access": {
"type": "object",
"additionalProperties": false,
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "integer"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
}
},
"required": [
"host",
"port",
"username",
"password"
]
},
"sender": {
"type": "string"
},
"receivers": {
"type": "array",
"items": {
"type": "string"
}
},
"tags": {
"description": "list of strings, which will be placed in the e-mail subject",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"required": [
"access",
"sender",
"receivers"
]
}
},
"required": [
"kind",
"parameters"
]
},
{
"title": "check kind 'libnotify'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"libnotify"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"icon": {
"type": "string"
}
},
"required": []
}
},
"required": [
"kind",
"parameters"
]
}
]
},
"default": [
{
"kind": "console",
"parameters": {}
}
]
}
},
"required": []
},
"checks": {
"type": "array",
"items": {
"allOf": [
{
"description": "should represent a specific check",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"name": {
"type": "string"
},
"title": {
"type": "string"
},
"active": {
"type": "boolean",
"default": true
},
"threshold": {
"description": "how often a condition has to occur in order to be reported",
"type": "integer",
"minimum": 1,
"default": 3
},
"annoy": {
"description": "whether notifications shall be kept sending after the threshold has been surpassed",
"type": "boolean",
"default": false
},
"schedule": {
"type": "object",
"additionalProperties": false,
"properties": {
"regular_interval": {
"description": "in seconds or as text",
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0
},
{
"type": "string",
"enum": [
"minute",
"hour",
"day",
"week"
]
}
],
"default": 3600
},
"attentive_interval": {
"description": "in seconds or as text",
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0
},
{
"type": "string",
"enum": [
"minute",
"hour",
"day",
"week"
]
}
],
"default": 120
}
},
"required": []
},
"notifications": {
"type": "array",
"items": {
"anyOf": [
{
"title": "check kind 'console'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"console"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {},
"required": []
}
},
"required": [
"kind",
"parameters"
]
},
{
"title": "check kind 'file_touch'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"file_touch"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
]
}
},
"required": [
"kind",
"parameters"
]
},
{
"title": "check kind 'email'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"email"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"access": {
"type": "object",
"additionalProperties": false,
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "integer"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
}
},
"required": [
"host",
"port",
"username",
"password"
]
},
"sender": {
"type": "string"
},
"receivers": {
"type": "array",
"items": {
"type": "string"
}
},
"tags": {
"description": "list of strings, which will be placed in the e-mail subject",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"required": [
"access",
"sender",
"receivers"
]
}
},
"required": [
"kind",
"parameters"
]
},
{
"title": "check kind 'libnotify'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"libnotify"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"icon": {
"type": "string"
}
},
"required": []
}
},
"required": [
"kind",
"parameters"
]
}
]
},
"default": [
{
"kind": "console",
"parameters": {}
}
]
}
},
"required": [
"name"
]
},
{
"anyOf": [
{
"title": "notification channel 'script'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"script"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"path": {
"type": "string"
},
"arguments": {
"type": "array",
"item": {
"type": "string"
}
}
},
"required": [
"path"
]
}
},
"required": [
"kind",
"parameters"
]
},
{
"title": "notification channel 'file_timestamp'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"file_timestamp"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"path": {
"type": "string"
},
"warning_age": {
"type": "integer",
"exclusiveMinimum": 0,
"default": 3600
},
"critical_age": {
"type": "integer",
"exclusiveMinimum": 0,
"default": 86400
},
"condition_on_missing": {
"description": "which condition to report if file is missing",
"type": "string",
"enum": [
"unknown",
"ok",
"warning",
"critical"
],
"default": "warning"
},
"condition_on_implausible": {
"description": "which condition to report if the age is negative, i.e. the file is apparently from the future",
"type": "string",
"enum": [
"unknown",
"ok",
"warning",
"critical"
],
"default": "warning"
}
},
"required": [
"path"
]
}
},
"required": [
"kind",
"parameters"
]
},
{
"title": "notification channel 'http_request'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"http_request"
]
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"request": {
"type": "object",
"additionalProperties": false,
"properties": {
"target": {
"description": "URL",
"type": "string"
},
"method": {
"type": "string",
"enum": [
"GET",
"POST"
],
"default": "GET"
}
},
"required": [
"target"
]
},
"response": {
"type": "object",
"additionalProperties": false,
"properties": {
"status_code": {
"description": "checks whether the response status code is this",
"type": [
"null",
"integer"
],
"default": 200
},
"headers": {
"description": "conjunctively checks header key-value pairs",
"type": "object",
"additionalProperties": {
"description": "header value",
"type": "string"
},
"properties": {},
"required": [],
"default": {}
},
"body_part": {
"description": "checks whether the response body contains this string",
"type": "string"
}
},
"required": []
},
"as_warning": {
"description": "whether a violation of this check shall be exposed as warning instead of critical; default: false",
"type": "boolean",
"default": false
}
},
"required": [
"request"
]
}
},
"required": [
"kind",
"parameters"
]
}
]
}
]
}
}
},
"required": [
"defaults",
"checks"
]
}

View file

@ -1,12 +1,13 @@
{
"defaults": {
},
"checks": {
"test": {
"checks": [
{
"name": "test",
"threshold": 1,
"annoy": true,
"schedule": {
"regular_interval": 60,
"regular_interval": 15,
"attentive_interval": 5
},
"notifications": [
@ -18,7 +19,6 @@
{
"kind": "libnotify",
"parameters": {
"icon": "/home/fenris/bilder/zeug/heimdall.png"
}
}
],
@ -29,5 +29,5 @@
"critical_age": 120
}
}
}
]
}

View file

@ -1,489 +0,0 @@
{
"$defs": {
"active": {
"type": "boolean",
"default": true
},
"threshold": {
"description": "how often a condition has to occur in order to be reported",
"type": "integer",
"minimum": 1,
"default": 3
},
"annoy": {
"description": "whether notifications shall be kept sending after the threshold has been surpassed",
"type": "boolean",
"default": false
},
"schedule": {
"type": "object",
"additionalProperties": false,
"properties": {
"regular_interval": {
"description": "in seconds or as text: minute, hour, day, week",
"type": ["integer", "string"]
},
"attentive_interval": {
"description": "in seconds or as text: minute, hour, day, week",
"type": ["integer", "string"]
}
},
"required": [
"regular_interval"
]
},
"check_kind_test": {
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "test"
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"condition": {
"type": "string",
"enum": [
"unknown",
"ok",
"warning",
"critical"
],
"default": "warning"
},
"output": {
"type": "string",
"default": ""
}
},
"required": [
]
}
},
"required": [
"kind",
"parameters"
]
},
"check_kind_script": {
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "script"
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"path": {
"type": "string"
},
"arguments": {
"type": "array",
"item": {
"type": "string"
}
}
},
"required": [
"path"
]
}
},
"required": [
"kind",
"parameters"
]
},
"check_kind_file_timestamp": {
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "file_timestamp"
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"path": {
"type": "string"
},
"warning_age": {
"type": "integer",
"exclusiveMinimum": 0,
"default": 3600
},
"critical_age": {
"type": "integer",
"exclusiveMinimum": 0,
"default": 86400
},
"condition_on_missing": {
"description": "which condition to report if file is missing",
"type": "string",
"enum": [
"unknown",
"ok",
"warning",
"critical"
],
"default": "warning"
},
"condition_on_implausible": {
"description": "which condition to report if the age is negative, i.e. the file is apparently from the future",
"type": "string",
"enum": [
"unknown",
"ok",
"warning",
"critical"
],
"default": "warning"
}
},
"required": [
"path"
]
}
},
"required": [
"kind",
"parameters"
]
},
"check_kind_http_request": {
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "http_request"
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"request": {
"type": "object",
"additionalProperties": false,
"properties": {
"target": {
"description": "URL",
"type": "string"
},
"method": {
"type": "string",
"enum": [
"GET",
"POST"
],
"default": "GET"
}
},
"required": [
"target"
]
},
"response": {
"type": "object",
"additionalProperties": false,
"properties": {
"status_code": {
"description": "checks whether the response status code is this",
"type": ["null", "integer"],
"default": 200
},
"headers": {
"description": "conjunctively checks header key-value pairs",
"type": "object",
"additionalProperties": {
"description": "header value",
"type": "string"
},
"properties": {
},
"required": [
],
"default": {}
},
"body_part": {
"description": "checks whether the response body contains this string",
"type": "string"
}
},
"required": [
]
},
"as_warning": {
"description": "whether a violation of this check shall be exposed as warning instead of critical; default: false",
"type": "boolean",
"default": false
}
},
"required": [
"request"
]
}
},
"required": [
"kind",
"parameters"
]
},
"notification_channel_console": {
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "console"
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
},
"required": [
]
}
},
"required": [
"kind",
"parameters"
]
},
"notification_channel_file_touch": {
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "file_touch"
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
]
}
},
"required": [
"kind",
"parameters"
]
},
"notification_channel_libnotify": {
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "libnotify"
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"icon": {
"type": "string"
}
},
"required": [
]
}
},
"required": [
"kind",
"parameters"
]
},
"notification_channel_email": {
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "email"
},
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"access": {
"type": "object",
"additionalProperties": false,
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "integer"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
}
},
"required": [
"host",
"port",
"username",
"password"
]
},
"sender": {
"type": "string"
},
"receivers": {
"type": "array",
"item": {
"type": "string"
}
},
"tags": {
"description": "list of strings, which will be placed in the e-mail subject",
"type": "array",
"item": {
"type": "string"
},
"default": []
}
},
"required": [
"access",
"sender",
"receivers"
]
}
},
"required": [
"kind",
"parameters"
]
},
"notifications": {
"type": "array",
"item": {
"anyOf": [
{
"$ref": "#/$defs/notification_channel_console"
},
{
"$ref": "#/$defs/notification_channel_file_touch"
},
{
"$ref": "#/$defs/notification_channel_email"
}
]
},
"default": [
{
"kind": "console",
"parameters": {
}
}
]
}
},
"type": "object",
"additionalProperties": false,
"properties": {
"defaults": {
"description": "default values for checks",
"type": "object",
"additionalProperties": false,
"properties": {
"active": {
"$ref": "#/$defs/active"
},
"threshold": {
"$ref": "#/$defs/threshold"
},
"annoy": {
"$ref": "#/$defs/annoy"
},
"schedule": {
"$ref": "#/$defs/schedule"
},
"notifications": {
"$ref": "#/$defs/notifications"
}
},
"required": [
]
},
"checks": {
"type": "object",
"additionalProperties": {
"allOf": [
{
"description": "should represent a specific check",
"type": "object",
"additionalProperties": false,
"properties": {
"title": {
"type": "string"
},
"active": {
"$ref": "#/$defs/active"
},
"threshold": {
"$ref": "#/$defs/threshold"
},
"annoy": {
"$ref": "#/$defs/annoy"
},
"schedule": {
"$ref": "#/$defs/schedule"
},
"notifications": {
"$ref": "#/$defs/notifications"
}
},
"required": [
]
},
{
"anyOf": [
{
"$ref": "#/$defs/check_kind_test"
},
{
"$ref": "#/$defs/check_kind_script"
},
{
"$ref": "#/$defs/check_kind_file_timestamp"
},
{
"$ref": "#/$defs/check_kind_http_request"
}
]
}
]
},
"properties": {
},
"required": [
]
}
},
"required": [
"defaults",
"groups"
]
}

View file

@ -1,5 +1,9 @@
class interface_check_kind(object):
def parameters_schema(self):
raise NotImplementedError
def normalize_conf_node(self, node):
raise NotImplementedError

View file

@ -1,5 +1,55 @@
class implementation_check_kind_file_timestamp(interface_check_kind):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"path": {
"type": "string"
},
"warning_age": {
"type": "integer",
"exclusiveMinimum": 0,
"default": (60 * 60)
},
"critical_age": {
"type": "integer",
"exclusiveMinimum": 0,
"default": (60 * 60 * 24)
},
"condition_on_missing": {
"description": "which condition to report if file is missing",
"type": "string",
"enum": [
"unknown",
"ok",
"warning",
"critical"
],
"default": "warning"
},
"condition_on_implausible": {
"description": "which condition to report if the age is negative, i.e. the file is apparently from the future",
"type": "string",
"enum": [
"unknown",
"ok",
"warning",
"critical"
],
"default": "warning"
}
},
"required": [
"path"
]
}
'''
[implementation]
'''
@ -62,4 +112,3 @@ class implementation_check_kind_file_timestamp(interface_check_kind):
),
}

View file

@ -1,5 +1,76 @@
class implementation_check_kind_http_request(interface_check_kind):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"request": {
"type": "object",
"additionalProperties": False,
"properties": {
"target": {
"description": "URL",
"type": "string"
},
"method": {
"type": "string",
"enum": [
"GET",
"POST"
],
"default": "GET"
}
},
"required": [
"target"
]
},
"response": {
"type": "object",
"additionalProperties": False,
"properties": {
"status_code": {
"description": "checks whether the response status code is this",
"type": ["null", "integer"],
"default": 200
},
"headers": {
"description": "conjunctively checks header key-value pairs",
"type": "object",
"additionalProperties": {
"description": "header value",
"type": "string"
},
"properties": {
},
"required": [
],
"default": {}
},
"body_part": {
"description": "checks whether the response body contains this string",
"type": "string"
}
},
"required": [
]
},
"as_warning": {
"description": "whether a violation of this check shall be exposed as warning instead of critical; default: false",
"type": "boolean",
"default": False
}
},
"required": [
"request"
]
}
'''
[implementation]
'''
@ -118,3 +189,4 @@ class implementation_check_kind_http_request(interface_check_kind):
),
"output": "\n".join(lines),
}

View file

@ -1,5 +1,29 @@
class implementation_check_kind_script(interface_check_kind):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"path": {
"type": "string"
},
"arguments": {
"type": "array",
"item": {
"type": "string"
}
}
},
"required": [
"path"
]
}
'''
[implementation]
'''

View file

@ -5,7 +5,7 @@ def state_encode(state):
"count": state["count"],
}
def state_decode(state_encoded):
return {
"timestamp": state_encoded["timestamp"],
@ -14,6 +14,179 @@ def state_decode(state_encoded):
}
def schema_active():
return {
"type": "boolean",
"default": True
}
def schema_threshold():
return {
"description": "how often a condition has to occur in order to be reported",
"type": "integer",
"minimum": 1,
"default": 3
}
def schema_annoy():
return {
"description": "whether notifications shall be kept sending after the threshold has been surpassed",
"type": "boolean",
"default": False
}
def schema_interval(default):
return {
"description": "in seconds or as text",
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0
},
{
"type": "string",
"enum": [
"minute",
"hour",
"day",
"week",
]
},
],
"default": default,
}
def schema_schedule():
return {
"type": "object",
"additionalProperties": False,
"properties": {
"regular_interval": schema_interval(60 * 60),
"attentive_interval": schema_interval(60 * 2),
},
"required": [
]
}
def schema_notifications(notification_channel_implementations):
return {
"type": "array",
"items": {
"anyOf": list(
map(
lambda pair: {
"title": ("check kind '%s'" % pair[0]),
"type": "object",
"unevaluatedProperties": False,
"properties": {
"kind": {
"type": "string",
"enum": [pair[0]]
},
"parameters": pair[1].parameters_schema(),
},
"required": [
"kind",
"parameters"
]
},
notification_channel_implementations.items()
)
)
},
"default": [
{
"kind": "console",
"parameters": {
}
}
]
}
def schema_root(check_kind_implementations, notification_channel_implementations):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"defaults": {
"description": "default values for checks",
"type": "object",
"additionalProperties": False,
"properties": {
"active": schema_active(),
"threshold": schema_threshold(),
"annoy": schema_annoy(),
"schedule": schema_schedule(),
"notifications": schema_notifications(notification_channel_implementations),
},
"required": [
]
},
"checks": {
"type": "array",
"items": {
"allOf": [
{
"description": "should represent a specific check",
"type": "object",
"unevaluatedProperties": False,
"properties": {
"name": {
"type": "string"
},
"title": {
"type": "string"
},
"active": schema_active(),
"threshold": schema_threshold(),
"annoy": schema_annoy(),
"schedule": schema_schedule(),
"notifications": schema_notifications(notification_channel_implementations),
},
"required": [
"name",
]
},
{
"anyOf": list(
map(
lambda pair: {
"title": ("notification channel '%s'" % pair[0]),
"type": "object",
"unevaluatedProperties": False,
"properties": {
"kind": {
"type": "string",
"enum": [pair[0]]
},
"parameters": pair[1].parameters_schema(),
},
"required": [
"kind",
"parameters"
]
},
check_kind_implementations.items()
)
),
},
]
}
}
},
"required": [
"defaults",
"checks",
]
}
def conf_normalize_interval(interval_raw):
if (type(interval_raw) == int):
return interval_raw
@ -46,8 +219,15 @@ def conf_normalize_schedule(node):
}
def conf_normalize_defaults(node):
return dict_merge(
def conf_normalize_notification(notification_channel_implementations, node):
return {
"kind": node["kind"],
"parameters": notification_channel_implementations[node["kind"]].normalize_conf_node(node["parameters"]),
}
def conf_normalize_defaults(notification_channel_implementations, node):
node_ = dict_merge(
{
"active": True,
"threshold": 3,
@ -61,54 +241,88 @@ def conf_normalize_defaults(node):
},
node
)
def conf_normalize_check(check_kind_implementations, defaults, name, node):
if ("kind" not in node):
raise ValueError("missing mandatory 'check' field 'kind'")
else:
if (node["kind"] not in check_kind_implementations):
raise ValueError("unhandled kind: %s" % node["kind"])
else:
node_ = dict_merge(
{
"title": name,
"active": defaults["active"],
"threshold": defaults["threshold"],
"annoy": defaults["annoy"],
"schedule": defaults["schedule"],
"notifications": defaults["notifications"],
"parameters": {},
},
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"]
)
return {
"title": node_["title"],
"active": node_["active"],
"threshold": node_["threshold"],
"annoy": node_["annoy"],
"schedule": conf_normalize_schedule(node_["schedule"]),
"notifications": node_["notifications"],
"kind": node_["kind"],
"parameters": check_kind_implementations[node_["kind"]].normalize_conf_node(node_["parameters"]),
}
),
}
def conf_normalize_root(check_kind_implementations, node):
return dict(
map(
lambda check_pair: (
check_pair[0],
conf_normalize_check(
check_kind_implementations,
conf_normalize_defaults(node["defaults"]),
check_pair[0],
check_pair[1]
),
),
node["checks"].items()
def conf_normalize_check(check_kind_implementations, notification_channel_implementations, defaults, node):
if ("name" not in node):
raise ValueError("missing mandatory 'check' field 'name'")
else:
if ("kind" not in node):
raise ValueError("missing mandatory 'check' field 'kind'")
else:
if (node["kind"] not in check_kind_implementations):
raise ValueError("unhandled kind: %s" % node["kind"])
else:
node_ = dict_merge(
{
"title": node["name"],
"active": defaults["active"],
"threshold": defaults["threshold"],
"annoy": defaults["annoy"],
"schedule": defaults["schedule"],
"notifications": defaults["notifications"],
"parameters": {},
},
node
)
return {
"name": node_["name"],
"title": node_["title"],
"active": node_["active"],
"threshold": node_["threshold"],
"annoy": node_["annoy"],
"schedule": conf_normalize_schedule(node_["schedule"]),
"notifications": list(
map(
lambda x: conf_normalize_notification(notification_channel_implementations, x),
node_["notifications"]
)
),
"kind": node_["kind"],
"parameters": check_kind_implementations[node_["kind"]].normalize_conf_node(node_["parameters"]),
}
def conf_normalize_root(check_kind_implementations, notification_channel_implementations, node):
counts = {}
for node_ in node["checks"]:
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}}",
{
"names": ",".join(counts.keys()),
}
)
)
else:
return list(
map(
lambda node_: conf_normalize_check(
check_kind_implementations,
notification_channel_implementations,
conf_normalize_defaults(notification_channel_implementations, node["defaults"]),
node_
),
node["checks"]
)
)
)
def main():
@ -127,7 +341,7 @@ def main():
help = "path to the configuration file"
)
argumentparser.add_argument(
"-s",
"-f",
"--state-path",
type = str,
default = None,
@ -143,6 +357,14 @@ def main():
dest = "erase_state",
help = "whether the state shall be deleted on start; this will cause that all checks are executed"
)
argumentparser.add_argument(
"-s",
"--show-schema",
action = "store_true",
default = False,
dest = "show_schema",
help = "print the hmdl JSON schema to stdout and exit"
)
argumentparser.add_argument(
"-e",
"--expose-full-conf",
@ -166,8 +388,6 @@ def main():
## exec
_sys.stderr.write(">> state file path: %s\n" % state_path)
### load check kind implementations
check_kind_implementations = {
"script": implementation_check_kind_script(),
@ -183,111 +403,131 @@ def main():
"libnotify": implementation_notification_channel_libnotify(),
}
### get configuration data
checks = conf_normalize_root(check_kind_implementations, _json.loads(file_read(args.conf_path)))
if (args.expose_full_conf):
_sys.stdout.write(_json.dumps(checks, indent = "\t") + "\n")
_sys.exit(1)
if (args.show_schema):
_sys.stdout.write(
_json.dumps(
schema_root(
check_kind_implementations,
notification_channel_implementations
),
indent = "\t"
)
+
"\n"
)
else:
### get state data
if (
(not _os.path.exists(state_path))
or
args.erase_state
):
state_data = {}
file_write(state_path, _json.dumps(state_data, indent = "\t"))
else:
state_data = _json.loads(file_read(state_path))
_sys.stderr.write(">> state file path: %s\n" % state_path)
### iterate through checks
for (check_name, check_data, ) in checks.items():
if (not check_data["active"]):
pass
### get configuration data
checks = conf_normalize_root(
check_kind_implementations,
notification_channel_implementations,
_json.loads(file_read(args.conf_path))
)
if (args.expose_full_conf):
_sys.stdout.write(_json.dumps(checks, indent = "\t") + "\n")
_sys.exit(1)
else:
### get state data
if (
(not _os.path.exists(state_path))
or
args.erase_state
):
state_data = {}
file_write(state_path, _json.dumps(state_data, indent = "\t"))
else:
### get old state and examine whether the check shall be executed
old_item_state = (
None
if (check_name not in state_data) else
state_decode(state_data[check_name])
)
timestamp = get_current_timestamp()
due = (
(old_item_state is None)
or
(old_item_state["condition"] != enum_condition.ok)
or
((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["regular_interval"])
or
(
(old_item_state["count"] is not None)
and
((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["attentive_interval"])
)
)
if (not due):
state_data = _json.loads(file_read(state_path))
### iterate through checks
for check_data in checks:
if (not check_data["active"]):
pass
else:
_sys.stderr.write(
string_coin(
"-- {{check_name}}\n",
{
"check_name": check_name,
}
)
### get old state and examine whether the check shall be executed
old_item_state = (
None
if (check_data["name"] not in state_data) else
state_decode(state_data[check_data["name"]])
)
### execute check and set new state
result = check_kind_implementations[check_data["kind"]].run(check_data["parameters"])
new_item_state = {
"timestamp": timestamp,
"condition": result["condition"],
"count": (
1
if (
(old_item_state is None)
or
(old_item_state["condition"] != result["condition"])
) else
(
(old_item_state["count"] + 1)
if (
(old_item_state["count"] is not None)
and
((old_item_state["count"] + 1) <= check_data["threshold"])
) else
None
)
),
}
state_data[check_name] = state_encode(new_item_state)
file_write(state_path, _json.dumps(state_data, indent = "\t"))
### send notifications
if (
(
(new_item_state["count"] is not None)
and
(new_item_state["count"] == check_data["threshold"])
)
timestamp = get_current_timestamp()
due = (
(old_item_state is None)
or
(old_item_state["condition"] != enum_condition.ok)
or
((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["regular_interval"])
or
(
(new_item_state["count"] is None)
(old_item_state["count"] is not None)
and
check_data["annoy"]
((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["attentive_interval"])
)
):
for notification in check_data["notifications"]:
if (notification["kind"] in notification_channel_implementations):
notification_channel_implementations[notification["kind"]].notify(
notification["parameters"],
check_name,
check_data,
new_item_state,
result["output"]
)
if (not due):
pass
else:
_sys.stderr.write(
string_coin(
"-- {{check_name}}\n",
{
"check_name": check_data["name"],
}
)
)
### execute check and set new state
result = check_kind_implementations[check_data["kind"]].run(check_data["parameters"])
new_item_state = {
"timestamp": timestamp,
"condition": result["condition"],
"count": (
1
if (
(old_item_state is None)
or
(old_item_state["condition"] != result["condition"])
) else
(
(old_item_state["count"] + 1)
if (
(old_item_state["count"] is not None)
and
((old_item_state["count"] + 1) <= check_data["threshold"])
) else
None
)
else:
raise ValueError("invalid notification kind: %s" % notification["kind"])
),
}
state_data[check_data["name"]] = state_encode(new_item_state)
file_write(state_path, _json.dumps(state_data, indent = "\t"))
### send notifications
if (
(
(new_item_state["count"] is not None)
and
(new_item_state["count"] == check_data["threshold"])
)
or
(
(new_item_state["count"] is None)
and
check_data["annoy"]
)
):
for notification in check_data["notifications"]:
if (notification["kind"] in notification_channel_implementations):
notification_channel_implementations[notification["kind"]].notify(
notification["parameters"],
check_data["name"],
check_data,
new_item_state,
result["output"]
)
else:
raise ValueError("invalid notification kind: %s" % notification["kind"])
main()

View file

@ -1,5 +1,9 @@
class interface_notification_channel(object):
def parameters_schema(self):
raise NotImplementedError
def normalize_conf_node(self, node):
raise NotImplementedError

View file

@ -1,5 +1,19 @@
class implementation_notification_channel_console(interface_notification_channel):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
},
"required": [
]
}
'''
[implementation]
'''

View file

@ -1,5 +1,63 @@
class implementation_notification_channel_email(interface_notification_channel):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"access": {
"type": "object",
"additionalProperties": False,
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "integer"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
}
},
"required": [
"host",
"port",
"username",
"password"
]
},
"sender": {
"type": "string"
},
"receivers": {
"type": "array",
"items": {
"type": "string"
}
},
"tags": {
"description": "list of strings, which will be placed in the e-mail subject",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"required": [
"access",
"sender",
"receivers"
]
}
'''
[implementation]
'''

View file

@ -1,5 +1,23 @@
class implementation_notification_channel_file_touch(interface_notification_channel):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
]
}
'''
[implementation]
'''

View file

@ -1,12 +1,29 @@
class implementation_notification_channel_libnotify(interface_notification_channel):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"icon": {
"type": "string"
}
},
"required": [
]
}
'''
[implementation]
'''
def normalize_conf_node(self, node):
return dict_merge(
{
"icon": None,
"icon": "/usr/local/share/icons/heimdall.png",
},
node
)

View file

@ -3,8 +3,8 @@
- Selbst-Test
- Benachrichtigungs-Kanäle:
- Matrix
- JSON-Schema für Konfiguration von Programm erzeugen lassen
- Möglichkeit dauerhaft laufen zulassen (evtl. als systemd-Dienst)
- Versionierung
- Umbenennung: `output` zu `info`
- Test-Routinen