initial upload of version 2.1.0 which uses the new plugin API

This commit is contained in:
Ralf Mellis 2025-04-05 11:47:59 +02:00
parent 358f84307c
commit cc2aaee908
34 changed files with 1204 additions and 1238 deletions

View File

@ -12,22 +12,21 @@ Mailboxes with quota usage, number of messages, last logins via IMAP/POP3/SMTP
## Caveats: ## Caveats:
Tested only with dockerized versions of Mailcow Tested only with dockerized versions of Mailcow
Tested with Mailcow version 2022-07a and higher Tested with Mailcow version 2023-01 and higher
Please feel free to add other working environments via review/email. Please feel free to add other working environments via review/email.
## Upgrade from older MKPs (before 1.2.0): Upgrade from older MKPs (before 2.1.0): If you upgrade from a already installed version before 2.1.0, you have to re-create your rules for the "Mailcow Server Information" (reason: parameter handling changes and migration to the new plugin API).
If you upgrade from a already installed version before 1.2.0, you have to re-create your rules for the "Mailcow Server Information" (reason: massive parameter changes). HINT: It is a good idea to take a screenshot from all other Mailcow related rules as well.
## Steps to accomplish this without problems: Steps to accomplish this without problems:
1. Take a screenshot of your settings in the above mentioned ruleset
2. Assure that you have access to the API keys you used within the current rules
3. Delete all rules for "Mailcow Server Information"
4. Install and enable the new MKP
5. Re-create your rules with the previously saved information from steps 1 and 2
6. Apply your changes
Take a screenshot of your settings in the above mentioned ruleset
Assure that you have access to the API keys you used within the current rules
Delete all rules for "Mailcow Server Information"
Install and enable the new MKP
Re-create your rules with the previously saved information from steps 1 and 2
Apply your changes
## General installation instructions: ## General installation instructions:
@ -57,6 +56,8 @@ If you upgrade from a already installed version before 1.2.0, you have to re-cre
## Version history: ## Version history:
2025/03/30 2.1.0 Migration to new plugin API done
2024/01/26 1.2.0 Added password store option for the API key, added version check 2024/01/26 1.2.0 Added password store option for the API key, added version check
2023/09/22: 1.1.4 Bugfix for the last bugfix :-) 2023/09/22: 1.1.4 Bugfix for the last bugfix :-)

View File

@ -1,162 +0,0 @@
#!/usr/bin/env python3
import time
import random
from pprint import pprint
from .agent_based_api.v1 import get_value_store, get_rate, register, render, Service, Result, State, Metric
def getStateUpper(levels, value):
warn, crit = levels
if value >= crit:
return State.CRIT
if value >= warn:
return State.WARN
return State.OK
# services with item --> storageid and usage of value_store (to be able to calculate rates)
def discover_mailcow_domains(section):
for key in section:
yield(Service(item = key))
def check_mailcow_domains(item, params, section):
domain = item
# get all values from section
active = section[domain][0]
create_time = section[domain][1]
modify_time = section[domain][2]
max_number_of_mailboxes = section[domain][3]
number_of_mailboxes = section[domain][4]
percent_used_mailboxes = section[domain][5]
max_number_of_aliases = section[domain][6]
number_of_aliases = section[domain][7]
percent_used_aliases = section[domain][8]
total_number_of_messages = section[domain][9]
total_number_of_bytes_used = section[domain][10]
quota = section[domain][11]
percent_storage_used_for_messages = section[domain][12]
# create (main) service for used storage (domain quota)
warn, crit = params["levels_mailcow_domains_quota_used"]
levels = (warn, crit)
state_quota = getStateUpper(levels, percent_storage_used_for_messages)
# create graph for used quota
yield Metric("mailcow_domains_used_quota", percent_storage_used_for_messages, levels=levels)
summary_quota = f"Storage quota used is {render.percent(percent_storage_used_for_messages)}"
details_quota = f"Storage quota: {render.bytes(total_number_of_bytes_used)} of {render.bytes(quota)} used"
# create service
yield(Result(state=state_quota, summary=summary_quota, details=details_quota))
# create some additional services and information only details
notice = f"Active: {active}"
yield(Result(state=State.OK, notice=notice))
notice = f"Creation date: {create_time}"
yield(Result(state=State.OK, notice=notice))
notice = f"Last modified: {modify_time}"
yield(Result(state=State.OK, notice=notice))
# create service for number of configured mailboxes (percent)
warn, crit = params["levels_mailcow_domains_mailboxes_used"]
levels = (warn, crit)
state_mailboxes = getStateUpper(levels, percent_used_mailboxes)
yield Metric("mailcow_domains_mailboxes", percent_used_mailboxes, levels=levels)
notice = f"Used mailboxes: {render.percent(percent_used_mailboxes)}, {number_of_mailboxes} of {max_number_of_mailboxes} in use"
yield(Result(state=state_mailboxes, notice=notice))
# create service for number of configured aliases (percent)
warn, crit = params["levels_mailcow_domains_aliases_used"]
levels = (warn, crit)
state_aliases = getStateUpper(levels, percent_used_aliases)
yield Metric("mailcow_domains_aliases", percent_used_aliases, levels=levels)
notice = f"Used aliases: {render.percent(percent_used_aliases)}, {number_of_aliases} of {max_number_of_aliases} in use"
yield(Result(state=state_aliases, notice=notice))
# create service for number of messages
warn, crit = params["levels_mailcow_domains_num_messages"]
levels = (warn, crit)
state_messages = getStateUpper(levels, total_number_of_messages)
yield Metric("mailcow_domains_messages", total_number_of_messages, levels=levels)
notice = f"Number of messages: {total_number_of_messages}"
yield(Result(state=state_messages, notice=notice))
# create service for number of configured aliases (absolute)
warn, crit = params["levels_mailcow_domains_num_aliases"]
levels = (warn, crit)
state_aliases = getStateUpper(levels, number_of_aliases)
yield Metric("mailcow_domains_configured_aliases", number_of_aliases, levels=levels)
notice = f"Number of aliases: {number_of_aliases}, max {max_number_of_aliases} allowed"
yield(Result(state=state_aliases, notice=notice))
# create service for number of configured mailboxes (absolute)
warn, crit = params["levels_mailcow_domains_num_mailboxes"]
levels = (warn, crit)
state_mailboxes = getStateUpper(levels, number_of_mailboxes)
yield Metric("mailcow_domains_configured_mailboxes", number_of_mailboxes, levels=levels)
notice = f"Number of mailboxes: {number_of_mailboxes}, max {max_number_of_mailboxes} allowed"
yield(Result(state=state_mailboxes, notice=notice))
def parse_mailcow_domains_section(string_table):
# convert the raw output of the agent section into a meaningful structure, do type conversions and so on
parsed_data = {}
for line in string_table:
domainname = line[0]
value_active = int(line[1])
if value_active == 1:
active = "yes"
else:
active = "no"
# calculate creation and last modification date in human readable format
create_time_value = line[2]
if create_time_value == "None":
create_time_data = "Not available"
else:
create_time_data = create_time_value
modify_time_value = line[3]
if modify_time_value == "None":
modify_time_data = "Never"
else:
modify_time_data = modify_time_value
# calculate percentage of used mailboxes
max_number_of_mailboxes = int(line[4])
number_of_mailboxes = int(line[5])
percent_used_mailboxes = number_of_mailboxes * 100 / max_number_of_mailboxes
# calculate percentage of used aliases
max_number_of_aliases = int(line[6])
number_of_aliases = int(line[7])
percent_used_aliases = number_of_aliases * 100 / max_number_of_aliases
# number of messages within domain
total_number_of_messages = int(line[8])
# calculate storage used for all messages in domain
total_number_of_bytes_used = int(line[9])
quota = int(line[10])
percent_storage_used_for_messages = total_number_of_bytes_used * 100 / quota
# store all (calculated) data
parsed_data[f"{domainname}"] = [active, create_time_data, modify_time_data,
max_number_of_mailboxes, number_of_mailboxes, percent_used_mailboxes,
max_number_of_aliases, number_of_aliases, percent_used_aliases,
total_number_of_messages,
total_number_of_bytes_used, quota, percent_storage_used_for_messages
]
return parsed_data
register.agent_section(
name = "mailcow_domains",
parse_function = parse_mailcow_domains_section,
)
register.check_plugin(
name = "mailcow_domains",
service_name = "Mailcow domain %s",
discovery_function = discover_mailcow_domains,
check_function = check_mailcow_domains,
check_default_parameters = {
"levels_mailcow_domains_quota_used": (65.0, 85.0),
"levels_mailcow_domains_mailboxes_used": (65.0, 85.0),
"levels_mailcow_domains_aliases_used": (65.0, 85.0),
"levels_mailcow_domains_num_messages": (10000, 25000),
"levels_mailcow_domains_num_aliases": (100, 250),
"levels_mailcow_domains_num_mailboxes": (100, 250),
},
check_ruleset_name="mailcow_domains",
)

View File

@ -1,142 +0,0 @@
#!/usr/bin/env python3
from pprint import pprint
from .agent_based_api.v1 import register, render, Service, Result, State, Metric
def getStateUpper(levels, value):
warn, crit = levels
if value >= crit:
return State.CRIT
if value >= warn:
return State.WARN
return State.OK
def getStateLower(levels, value):
warn, crit = levels
if value < crit:
return State.CRIT
if value < warn:
return State.WARN
return State.OK
def discover_mailcow_info(section):
yield (Service())
def check_mailcow_info(params, section):
for key in section:
if key == "mailcow":
# get thresholds
levels_num_domains = params["levels_num_domains"]
levels_num_mailboxes = params["levels_num_mailboxes"]
levels_num_global_messages = params["levels_num_global_messages"]
version = section[key]["version"]
git_version = section[key]["git_version"]
check_version_enabled = section[key]["check_version_enabled"]
update_available = section[key]["update_available"]
num_domains = section[key]["num_domains"]
num_mailboxes = section[key]["num_mailboxes"]
num_global_messages = section[key]["num_global_messages"]
# create graphs for number of domains, mailboxes and messages
yield (Metric("mc_num_domains", num_domains, levels=levels_num_domains))
yield (
Metric("mc_num_mailboxes", num_mailboxes, levels=levels_num_mailboxes)
)
yield (
Metric(
"mc_num_global_messages",
num_global_messages,
levels=levels_num_global_messages,
)
)
# create overall result
if check_version_enabled:
if update_available:
summary = f"Update available: Running version is {version}, Github version is: {git_version}"
state = State.WARN
else:
summary = f"No update available: Running version is {version}, Github version is: {git_version}"
state = State.OK
else:
summary = f"Version is {version}, Update check is disabled"
state = State.OK
details = f"Mailcow version: {version}\nNumber of domains: {num_domains}\nNumber of mailboxes: {num_mailboxes}\nNumber of messages: {num_global_messages}"
yield Result(state=state, summary=summary, details=details)
# Create result for number of domains
warn, crit = levels_num_domains
state = getStateUpper((warn, crit), num_domains)
notice = f"Number of domains: {num_domains}"
if state != State.OK:
yield (Result(state=state, notice=notice))
# Create result for number of mailboxes
warn, crit = levels_num_mailboxes
state = getStateUpper((warn, crit), num_mailboxes)
notice = f"Number of mailboxes: {num_mailboxes}"
if state != State.OK:
yield (Result(state=state, notice=notice))
# Create result for number of global messages
warn, crit = levels_num_global_messages
state = getStateUpper((warn, crit), num_global_messages)
notice = f"Number of messages: {num_global_messages}"
if state != State.OK:
yield (Result(state=state, notice=notice))
def parse_mailcow_info_section(string_table):
# pprint(string_table)
parsed_data = {
"mailcow": {},
}
# we expect only one line
line = string_table[0]
version = line[0]
num_domains = int(line[1])
num_mailboxes = int(line[2])
num_global_messages = int(line[3])
git_version = line[4]
update_available = line[5]
if update_available == "True":
update_available = True
else:
update_available = False
check_version_enabled = line[6]
if check_version_enabled == "True":
check_version_enabled = True
else:
check_version_enabled = False
parsed_data["mailcow"]["version"] = version
parsed_data["mailcow"]["num_domains"] = num_domains
parsed_data["mailcow"]["num_mailboxes"] = num_mailboxes
parsed_data["mailcow"]["num_global_messages"] = num_global_messages
parsed_data["mailcow"]["git_version"] = git_version
parsed_data["mailcow"]["update_available"] = update_available
parsed_data["mailcow"]["check_version_enabled"] = check_version_enabled
# pprint(parsed_data)
return parsed_data
register.agent_section(
name="mailcow_info",
parse_function=parse_mailcow_info_section,
)
register.check_plugin(
name="mailcow_info",
service_name="Mailcow Info",
discovery_function=discover_mailcow_info,
check_function=check_mailcow_info,
check_default_parameters={
"levels_num_domains": (100, 200),
"levels_num_mailboxes": (500, 1000),
"levels_num_global_messages": (100000, 250000),
},
check_ruleset_name="mailcow_info",
)

View File

@ -1,155 +0,0 @@
#!/usr/bin/env python3
import time
import random
from pprint import pprint
from .agent_based_api.v1 import get_value_store, get_rate, register, render, Service, Result, State, Metric
def getStateUpper(levels, value):
warn, crit = levels
if value >= crit:
return State.CRIT
if value >= warn:
return State.WARN
return State.OK
def discover_mailcow_mailboxes(section):
for key in section:
yield(Service(item = key))
def check_mailcow_mailboxes(item, params, section):
mailbox = item
# get all values from section
active = section[mailbox][0]
create_time = section[mailbox][1]
modify_time = section[mailbox][2]
display_name = section[mailbox][3]
number_of_messages = section[mailbox][4]
_percent_in_use = section[mailbox][5]
quota = section[mailbox][6]
total_number_of_bytes_used = section[mailbox][7]
percent_storage_used_for_messages = section[mailbox][8]
last_imap_login_data = section[mailbox][9]
last_pop3_login_data = section[mailbox][10]
last_smtp_login_data = section[mailbox][11]
# create (main) service for used storage (mailbox quota)
warn, crit = params["levels_mailcow_mailboxes_quota_used"]
levels = (warn, crit)
state_quota = getStateUpper(levels, percent_storage_used_for_messages)
# create graph for used quota
yield Metric("mailcow_mailboxes_used_quota", percent_storage_used_for_messages, levels=levels)
summary_quota = f"Storage quota for mailbox of '{display_name}' is {render.percent(percent_storage_used_for_messages)}"
details_quota = f"Quota: {render.bytes(total_number_of_bytes_used)} of {render.bytes(quota)} used"
# create service
yield(Result(state=state_quota, summary=summary_quota, details=details_quota))
# create some additional services and information only details
notice = f"Active: {active}"
yield(Result(state=State.OK, notice=notice))
notice = f"Creation date: {create_time}"
yield(Result(state=State.OK, notice=notice))
notice = f"Last modified: {modify_time}"
yield(Result(state=State.OK, notice=notice))
notice = f"Last IMAP login: {last_imap_login_data} ago"
yield(Result(state=State.OK, notice=notice))
notice = f"Last POP3 login: {last_pop3_login_data} ago"
yield(Result(state=State.OK, notice=notice))
notice = f"Last SMTP login: {last_smtp_login_data} ago"
yield(Result(state=State.OK, notice=notice))
# create service for number of messages
warn, crit = params["levels_mailcow_mailboxes_num_messages"]
levels = (warn, crit)
state_messages = getStateUpper(levels, number_of_messages)
yield Metric("mailcow_mailboxes_messages", number_of_messages, levels=levels)
notice = f"Number of messages: {number_of_messages}"
yield(Result(state=state_messages, notice=notice))
def parse_mailcow_mailboxes_section(string_table):
# convert the raw output of the agent section into a meaningful structure, do type conversions and so on
parsed_data = {}
for line in string_table:
mailboxname = line[0]
value_active = int(line[1])
if value_active == 1:
active = "yes"
else:
active = "no"
# calculate creation and last modification date in human readable format
create_time_value = line[2]
if create_time_value == "None":
create_time_data = "Not available"
else:
create_time_data = create_time_value
modify_time_value = line[3]
if modify_time_value == "None":
modify_time_data = "Never"
else:
modify_time_data = modify_time_value
# get display name
display_name = line[4]
# number of messages within mailbox
number_of_messages = int(line[5])
# calculate storage used for all messages in mailbox
quota = int(line[7])
total_number_of_bytes_used = int(line[8])
if quota == 0:
# quota is not set, if this is the case, line[6] contains no numeric value, but the char "-"
# so set all usage counters to zero
percent_in_use = 0
percent_storage_used_for_messages = 0
else:
# percent in use, rounded to full percent (calculated by Mailcow)
percent_in_use = int(line[6])
# let's calculate our own value
percent_storage_used_for_messages = total_number_of_bytes_used * 100 / quota
# get time of last login for IMAP/POP3/SMTP (seconds since epoch)
last_imap_login = int(line[9])
last_pop3_login = int(line[10])
last_smtp_login = int(line[11])
# transfer these times into a human friendly format
if last_imap_login == 0:
last_imap_login_data = "Never"
else:
curr_time = int(time.time())
diff_time = curr_time - last_imap_login
last_imap_login_data = render.timespan(diff_time)
if last_pop3_login == 0:
last_pop3_login_data = "Never"
else:
curr_time = int(time.time())
diff_time = curr_time - last_pop3_login
last_pop3_login_data = render.timespan(diff_time)
if last_smtp_login == 0:
last_smtp_login_data = "Never"
else:
curr_time = int(time.time())
diff_time = curr_time - last_smtp_login
last_smtp_login_data = render.timespan(diff_time)
# store all (calculated) data
parsed_data[f"{mailboxname}"] = [active, create_time_data, modify_time_data,
display_name, number_of_messages, percent_in_use,
quota, total_number_of_bytes_used, percent_storage_used_for_messages,
last_imap_login_data, last_pop3_login_data, last_smtp_login_data
]
return parsed_data
register.agent_section(
name = "mailcow_mailboxes",
parse_function = parse_mailcow_mailboxes_section,
)
register.check_plugin(
name = "mailcow_mailboxes",
service_name = "Mailcow mailbox %s",
discovery_function = discover_mailcow_mailboxes,
check_function = check_mailcow_mailboxes,
check_default_parameters = {
"levels_mailcow_mailboxes_quota_used": (65.0, 85.0),
"levels_mailcow_mailboxes_num_messages": (1000, 2500),
},
check_ruleset_name="mailcow_mailboxes",
)

View File

@ -1,19 +0,0 @@
def agent_mailcow_arguments(params, hostname, ipaddress):
return [
"--hostname",
params["hostname"],
"--apikey",
passwordstore_get_cmdline("%s", params["apikey"]),
"--port",
params["port"],
"--check-version",
params["check_version"],
"--no-https",
params["no_https"],
"--no-cert-check",
params["no_cert_check"],
ipaddress,
]
special_agent_info["mailcow"] = agent_mailcow_arguments

View File

@ -1,72 +0,0 @@
#!/usr/bin/env python3
from cmk.gui.i18n import _
from cmk.gui.plugins.metrics import (
metric_info,
graph_info,
)
metric_info["mc_num_domains"] = {
"title": _("Number of Domains"),
"unit": "count",
"color": "44/a",
}
metric_info["mc_num_mailboxes"] = {
"title": _("Number of Mailboxes"),
"unit": "count",
"color": "44/b",
}
metric_info["mc_num_global_messages"] = {
"title": _("Number of Messages"),
"unit": "count",
"color": "42/a",
}
metric_info["mailcow_domains_used_quota"] = {
"title": _("Domain Quota Used"),
"unit": "%",
"color": "24/a",
}
metric_info["mailcow_domains_mailboxes"] = {
"title": _("Domain Mailboxes Quota Used"),
"unit": "%",
"color": "44/b",
}
metric_info["mailcow_domains_aliases"] = {
"title": _("Domain Aliases Quota Used"),
"unit": "%",
"color": "44/a",
}
metric_info["mailcow_domains_messages"] = {
"title": _("Global Number of Messages"),
"unit": "count",
"color": "24/a",
}
metric_info["mailcow_domains_configured_aliases"] = {
"title": _("Number of Configured Aliases"),
"unit": "count",
"color": "24/a",
}
metric_info["mailcow_domains_configured_mailboxes"] = {
"title": _("Number of Configured Mailboxes"),
"unit": "count",
"color": "24/b",
}
metric_info["mailcow_mailboxes_used_quota"] = {
"title": _("Mailbox Quota Used"),
"unit": "%",
"color": "24/a",
}
metric_info["mailcow_mailboxes_messages"] = {
"title": _("Number of Messages"),
"unit": "count",
"color": "24/b",
}

View File

@ -1,44 +0,0 @@
#!/usr/bin/env python3
from cmk.gui.plugins.metrics import perfometer_info
perfometer_info.append(
{
"type": "stacked",
"perfometers": [
{
"type": "linear",
"segments": [
"mc_num_domains",
"mc_num_mailboxes",
"mc_num_global_messages",
],
},
],
}
)
perfometer_info.append(
{
"type": "stacked",
"perfometers": [
{
"type": "linear",
"segments": ["mailcow_domains_used_quota"],
"total": 100.0,
},
],
}
)
perfometer_info.append(
{
"type": "stacked",
"perfometers": [
{
"type": "linear",
"segments": ["mailcow_mailboxes_used_quota"],
"total": 100.0,
},
],
}
)

View File

@ -1,115 +0,0 @@
from cmk.gui.i18n import _
from cmk.gui.plugins.wato import (
CheckParameterRulespecWithItem,
rulespec_registry,
RulespecGroupCheckParametersApplications
)
from cmk.gui.valuespec import (
Dictionary,
ListChoice,
TextAscii,
Percentage,
Tuple,
Float,
Integer
)
def _item_spec_mailcow_domains():
return TextAscii(
title=_("Domain name")
)
def _parameter_spec_mailcow_domains():
return Dictionary(
elements=[
("levels_mailcow_domains_quota_used", Tuple(
title=_("Mailcow domains quota usage for storage"),
elements=[
Percentage(
title=_("Warning at"),
default_value=65.0,
),
Percentage(
title=_("Critical at"),
default_value=85.0,
)
],
)),
("levels_mailcow_domains_mailboxes_used", Tuple(
title=_("Mailcow domains mailboxes usage"),
elements=[
Percentage(
title=_("Warning at"),
default_value=65.0,
),
Percentage(
title=_("Critical at"),
default_value=85.0,
)
],
)),
("levels_mailcow_domains_aliases_used", Tuple(
title=_("Mailcow domains aliases usage"),
elements=[
Percentage(
title=_("Warning at"),
default_value=65.0,
),
Percentage(
title=_("Critical at"),
default_value=85.0,
)
],
)),
("levels_mailcow_domains_num_messages", Tuple(
title=_("Number of messages"),
elements=[
Integer(
title=_("Warning at"),
default_value=10000,
),
Integer(
title=_("Critical at"),
default_value=25000,
)
],
)),
("levels_mailcow_domains_num_aliases", Tuple(
title=_("Number of configured aliases"),
elements=[
Integer(
title=_("Warning at"),
default_value=100,
),
Integer(
title=_("Critical at"),
default_value=250,
)
],
)),
("levels_mailcow_domains_num_mailboxes", Tuple(
title=_("Number of configured mailboxes"),
elements=[
Integer(
title=_("Warning at"),
default_value=100,
),
Integer(
title=_("Critical at"),
default_value=250,
)
],
)),
],
)
rulespec_registry.register(
CheckParameterRulespecWithItem(
check_group_name="mailcow_domains",
group=RulespecGroupCheckParametersApplications,
match_type="dict",
item_spec=_item_spec_mailcow_domains,
parameter_valuespec=_parameter_spec_mailcow_domains,
title=lambda: _("Levels for Mailcow domains"),
)
)

View File

@ -1,87 +0,0 @@
from cmk.gui.i18n import _
from cmk.gui.plugins.wato import (
CheckParameterRulespecWithoutItem,
rulespec_registry,
RulespecGroupCheckParametersApplications,
)
from cmk.gui.valuespec import (
Dictionary,
ListChoice,
Tuple,
Percentage,
Integer,
Float,
)
def _parameter_spec_mailcow_info():
return Dictionary(
elements=[
(
"levels_num_domains",
Tuple(
title=_("Number of email domains"),
elements=[
Integer(
title=_("Warning at"),
size=32,
default_value=100,
),
Integer(
title=_("Critical at"),
size=32,
default_value=200,
),
],
),
),
(
"levels_num_mailboxes",
Tuple(
title=_("Number of mailboxes"),
elements=[
Integer(
title=_("Warning at"),
size=32,
default_value=500,
),
Integer(
title=_("Critical at"),
size=32,
default_value=1000,
),
],
),
),
(
"levels_num_global_messages",
Tuple(
title=_("Number of messages"),
elements=[
Integer(
title=_("Warning at"),
size=32,
default_value=100000,
),
Integer(
title=_("Critical at"),
size=32,
default_value=250000,
),
],
),
),
],
)
rulespec_registry.register(
CheckParameterRulespecWithoutItem(
check_group_name="mailcow_info",
group=RulespecGroupCheckParametersApplications,
match_type="dict",
parameter_valuespec=_parameter_spec_mailcow_info,
title=lambda: _("Levels for Mailcow info"),
)
)

View File

@ -1,65 +0,0 @@
from cmk.gui.i18n import _
from cmk.gui.plugins.wato import (
CheckParameterRulespecWithItem,
rulespec_registry,
RulespecGroupCheckParametersApplications
)
from cmk.gui.valuespec import (
Dictionary,
ListChoice,
TextAscii,
Percentage,
Tuple,
Float,
Integer
)
def _item_spec_mailcow_mailboxes():
return TextAscii(
title=_("Mailbox name")
)
def _parameter_spec_mailcow_mailboxes():
return Dictionary(
elements=[
("levels_mailcow_mailboxes_quota_used", Tuple(
title=_("Mailcow mailbox quota usage for storage"),
elements=[
Percentage(
title=_("Warning at"),
default_value=65.0,
),
Percentage(
title=_("Critical at"),
default_value=85.0,
)
],
)),
("levels_mailcow_mailboxes_num_messages", Tuple(
title=_("Number of messages in mailbox"),
elements=[
Integer(
title=_("Warning at"),
size=32,
default_value=1000,
),
Integer(
title=_("Critical at"),
size=32,
default_value=2500,
)
],
)),
],
)
rulespec_registry.register(
CheckParameterRulespecWithItem(
check_group_name="mailcow_mailboxes",
group=RulespecGroupCheckParametersApplications,
match_type="dict",
item_spec=_item_spec_mailcow_mailboxes,
parameter_valuespec=_parameter_spec_mailcow_mailboxes,
title=lambda: _("Levels for Mailcow mailboxes"),
)
)

View File

@ -1,98 +0,0 @@
from cmk.gui.i18n import _
from cmk.gui.plugins.wato.special_agents.common import (
RulespecGroupDatasourceProgramsApps,
RulespecGroupDatasourceProgramsCustom,
RulespecGroupDatasourceProgramsHardware,
RulespecGroupDatasourceProgramsOS,
RulespecGroupDatasourceProgramsTesting,
)
from cmk.gui.plugins.wato.utils import (
IndividualOrStoredPassword,
rulespec_registry,
CheckParameterRulespecWithItem,
CheckParameterRulespecWithoutItem,
HostRulespec,
Rulespec,
)
from cmk.gui.valuespec import (
Dictionary,
ListChoice,
Checkbox,
TextAscii,
Password,
NetworkPort,
Integer,
)
def _valuespec_special_agent_mailcow():
return Dictionary(
title=_("Mailcow Server Information"),
help=_("Checking Mailcow instances via API"),
elements=[
(
"hostname",
TextAscii(
title=_("Hostname"),
size=32,
allow_empty=False,
help=_("Hostname of Mailcow server (bare FQDN or IP), mandatory"),
),
),
(
"apikey",
IndividualOrStoredPassword(
title=_("API Key"),
size=32,
allow_empty=False,
help=_("API Key, mandatory"),
),
),
(
"port",
TextAscii(
title=_("Port"),
allow_empty=True,
help=_("Specify port if not listening to HTTPS/HTTP, optional"),
),
),
(
"check_version",
Checkbox(
title=_("Check version"),
help=_("Checks the running version against the public Github repo"),
),
),
(
"no_https",
Checkbox(
title=_("Disable HTTPS"),
help=_(
"Activate to disable TLS encryption (not recommended), optional"
),
),
),
(
"no_cert_check",
Checkbox(
title=_("Disable certificate validation"),
help=_(
"Activate to disable certificate validation (not recommended), optional"
),
),
),
],
optional_keys=[],
)
rulespec_registry.register(
HostRulespec(
group=RulespecGroupDatasourceProgramsApps,
name="special_agents:mailcow",
valuespec=_valuespec_special_agent_mailcow,
)
)

View File

@ -0,0 +1,219 @@
#!/usr/bin/env python3
# pylint: disable=too-many-locals, line-too-long, too-many-statements
"""Mailcow check for domains"""
# import necessary elements from API version 2
from cmk.agent_based.v2 import (
AgentSection,
CheckPlugin,
Service,
Result,
State,
Metric,
render,
)
def get_state_upper(
levels: tuple[int | float, int | float], value: int | float
) -> State:
"""returns OK/WARN/CRIT depending on the given parameters"""
warn, crit = levels
if value >= crit:
return State.CRIT
if value >= warn:
return State.WARN
return State.OK
def parse_mailcow_domains(string_table):
"""the parse function"""
parsed_data = {}
# convert the raw output of the agent section to an meaningful structure
# do type conversions and so on
for line in string_table:
domainname = line[0]
value_active = int(line[1])
if value_active == 1:
active = "yes"
else:
active = "no"
# calculate creation and last modification date in human readable format
create_time_value = line[2]
if create_time_value == "None":
create_time_data = "Not available"
else:
create_time_data = create_time_value
modify_time_value = line[3]
if modify_time_value == "None":
modify_time_data = "Never"
else:
modify_time_data = modify_time_value
# calculate percentage of used mailboxes
max_number_of_mailboxes = int(line[4])
number_of_mailboxes = int(line[5])
percent_used_mailboxes = number_of_mailboxes * 100 / max_number_of_mailboxes
# calculate percentage of used aliases
max_number_of_aliases = int(line[6])
number_of_aliases = int(line[7])
percent_used_aliases = number_of_aliases * 100 / max_number_of_aliases
# number of messages within domain
total_number_of_messages = int(line[8])
# calculate storage used for all messages in domain
total_number_of_bytes_used = int(line[9])
quota = int(line[10])
percent_storage_used_for_messages = total_number_of_bytes_used * 100 / quota
# store all (calculated) data
parsed_data[f"{domainname}"] = [
active,
create_time_data,
modify_time_data,
max_number_of_mailboxes,
number_of_mailboxes,
percent_used_mailboxes,
max_number_of_aliases,
number_of_aliases,
percent_used_aliases,
total_number_of_messages,
total_number_of_bytes_used,
quota,
percent_storage_used_for_messages,
]
return parsed_data
def discover_mailcow_domains(section):
"""the discover function"""
# since we have a service with item here we must create one service per item
for key in section:
yield Service(item=key)
def check_mailcow_domains(item, params, section):
"""the check function"""
domain = section.get(item)
if not domain:
# if a previously found domain does not exist anymore, create a meaningful result
yield Result(
state=State.UNKNOWN,
summary="Domain not found anymore, maybe it was deleted?",
)
return
# get all infos regarding the domain
active = domain[0]
create_time = domain[1]
modify_time = domain[2]
max_number_of_mailboxes = domain[3]
number_of_mailboxes = domain[4]
percent_used_mailboxes = domain[5]
max_number_of_aliases = domain[6]
number_of_aliases = domain[7]
percent_used_aliases = domain[8]
total_number_of_messages = domain[9]
total_number_of_bytes_used = domain[10]
quota = domain[11]
percent_storage_used_for_messages = domain[12]
# create (main) service for used storage (domain quota)
_type, levels = params["levels_mailcow_domains_quota_used"]
state_quota = get_state_upper(levels, percent_storage_used_for_messages)
# create graph for used quota
yield Metric(
"mailcow_domains_used_quota", percent_storage_used_for_messages, levels=levels
)
summary_quota = (
f"Storage quota used is {render.percent(percent_storage_used_for_messages)}"
)
details_quota = f"Storage quota: {render.bytes(total_number_of_bytes_used)} of {render.bytes(quota)} used"
# create service
yield Result(state=state_quota, summary=summary_quota, details=details_quota)
# create some additional services and information only in service details
notice = f"Active: {active}"
yield Result(state=State.OK, notice=notice)
notice = f"Creation date: {create_time}"
yield Result(state=State.OK, notice=notice)
notice = f"Last modified: {modify_time}"
yield Result(state=State.OK, notice=notice)
# create service for number of configured mailboxes (percent)
_type, levels = params["levels_mailcow_domains_mailboxes_used"]
state_mailboxes = get_state_upper(levels, percent_used_mailboxes)
yield Metric("mailcow_domains_mailboxes", percent_used_mailboxes, levels=levels)
notice = f"Used mailboxes: {render.percent(percent_used_mailboxes)}, {number_of_mailboxes} of {max_number_of_mailboxes} in use"
yield Result(state=state_mailboxes, notice=notice)
# create service for number of configured aliases (percent)
_type, levels = params["levels_mailcow_domains_aliases_used"]
state_aliases = get_state_upper(levels, percent_used_aliases)
yield Metric("mailcow_domains_aliases", percent_used_aliases, levels=levels)
notice = f"Used aliases: {render.percent(percent_used_aliases)}, {number_of_aliases} of {max_number_of_aliases} in use"
yield Result(state=state_aliases, notice=notice)
# create service for number of messages
_type, levels = params["levels_mailcow_domains_num_messages"]
state_messages = get_state_upper(levels, total_number_of_messages)
yield Metric("mailcow_domains_messages", total_number_of_messages, levels=levels)
notice = f"Number of messages: {total_number_of_messages}"
yield Result(state=state_messages, notice=notice)
# create service for number of configured aliases (absolute)
_type, levels = params["levels_mailcow_domains_num_aliases"]
state_aliases = get_state_upper(levels, number_of_aliases)
yield Metric("mailcow_domains_configured_aliases", number_of_aliases, levels=levels)
notice = (
f"Number of aliases: {number_of_aliases}, max {max_number_of_aliases} allowed"
)
yield Result(state=state_aliases, notice=notice)
# create service for number of configured mailboxes (absolute)
_type, levels = params["levels_mailcow_domains_num_mailboxes"]
state_mailboxes = get_state_upper(levels, number_of_mailboxes)
yield Metric(
"mailcow_domains_configured_mailboxes", number_of_mailboxes, levels=levels
)
notice = f"Number of mailboxes: {number_of_mailboxes}, max {max_number_of_mailboxes} allowed"
yield Result(state=state_mailboxes, notice=notice)
# create the new agent section, must begin with "agent_section_"
# and must be an instance of "AgentSection"
agent_section_mailcow_domains = AgentSection(
# "name" must exactly match the section name within the agent output
name="mailcow_domains",
# define the parse function, name is arbitrary, a good choice is to choose
# "parse_" as prefix and append the section name
parse_function=parse_mailcow_domains,
)
# create the new check plugin, must begin with "check_plugin_"
# and must be an instance of "CheckPlugin"
check_plugin_mailcow_domains = CheckPlugin(
# "name" should be the same as the corresponding section within the agent output
name="mailcow_domains",
# this is a service with item, so you have to include a place holder for the item id
service_name="Mailcow domain %s",
# define the discovery function, name is arbitrary, a good choice is to choose
# "discover_" as prefix and append the section name
discovery_function=discover_mailcow_domains,
# define the check function, name is arbitrary, a good choice is to choose
# "check_" as prefix and append the section name
check_function=check_mailcow_domains,
# define the default parameters
check_default_parameters={
"levels_mailcow_domains_quota_used": ("fixed", (65.0, 85.0)),
"levels_mailcow_domains_mailboxes_used": ("fixed", (65.0, 85.0)),
"levels_mailcow_domains_aliases_used": ("fixed", (65.0, 85.0)),
"levels_mailcow_domains_num_messages": ("fixed", (10_000, 25_000)),
"levels_mailcow_domains_num_aliases": ("fixed", (100, 250)),
"levels_mailcow_domains_num_mailboxes": ("fixed", (100, 250)),
},
# connect to the ruleset where parameters can be defined
# must match the name of the ruleset exactly
check_ruleset_name="mailcow_domains",
)

View File

@ -0,0 +1,155 @@
#!/usr/bin/env python3
# pylint: disable=line-too-long, simplifiable-if-statement, missing-module-docstring, too-many-locals
# import necessary elements from API version 2
from cmk.agent_based.v2 import (
AgentSection,
CheckPlugin,
Service,
Result,
State,
Metric,
)
def get_state_upper(
levels: tuple[int | float, int | float], value: int | float
) -> State:
"""returns OK/WARN/CRIT depending on the given parameters"""
warn, crit = levels
if value >= crit:
return State.CRIT
if value >= warn:
return State.WARN
return State.OK
def parse_mailcow_info(string_table) -> dict:
"""the parse function"""
parsed_data = {}
# we only expect one line with 7 entries
line = string_table[0]
version = line[0]
num_domains = int(line[1])
num_mailboxes = int(line[2])
num_global_messages = int(line[3])
git_version = line[4]
update_available = line[5]
if update_available == "True":
update_available = True
else:
update_available = False
check_version_enabled = line[6]
if check_version_enabled == "True":
check_version_enabled = True
else:
check_version_enabled = False
parsed_data["version"] = version
parsed_data["num_domains"] = num_domains
parsed_data["num_mailboxes"] = num_mailboxes
parsed_data["num_global_messages"] = num_global_messages
parsed_data["git_version"] = git_version
parsed_data["update_available"] = update_available
parsed_data["check_version_enabled"] = check_version_enabled
return parsed_data
def discover_mailcow_info(section):
"""the discover function"""
yield Service()
def check_mailcow_info(params, section):
"""the check function"""
# get thresholds
_type, levels_num_domains = params["levels_num_domains"]
_type, levels_num_mailboxes = params["levels_num_mailboxes"]
_type, levels_num_global_messages = params["levels_num_global_messages"]
# get all section data
version: str = section["version"]
git_version: str = section["git_version"]
check_version_enabled: bool = section["check_version_enabled"]
update_available: bool = section["update_available"]
num_domains: int = section["num_domains"]
num_mailboxes: int = section["num_mailboxes"]
num_global_messages: int = section["num_global_messages"]
# create graphs for number of domains, mailboxes and messages
yield Metric(name="mc_num_domains", value=num_domains, levels=levels_num_domains)
yield Metric(
name="mc_num_mailboxes", value=num_mailboxes, levels=levels_num_mailboxes
)
yield Metric(
name="mc_num_global_messages",
value=num_global_messages,
levels=levels_num_global_messages,
)
# create overall result
if check_version_enabled:
if update_available:
summary = f"Update available: Running version is {version}, Github version is: {git_version}"
state = State.WARN
else:
summary = f"No update available: Running version is {version}, Github version is: {git_version}"
state = State.OK
else:
summary = f"Version is {version}, Update check is disabled"
state = State.OK
details = f"Mailcow version: {version}\nNumber of domains: {num_domains}\nNumber of mailboxes: {num_mailboxes}\nNumber of messages: {num_global_messages}"
yield Result(state=state, summary=summary, details=details)
# Create result for number of domains
warn, crit = levels_num_domains
state = get_state_upper((warn, crit), num_domains)
notice = f"Number of domains: {num_domains}"
if state != State.OK:
yield Result(state=state, notice=notice)
# Create result for number of mailboxes
warn, crit = levels_num_mailboxes
state = get_state_upper((warn, crit), num_mailboxes)
notice = f"Number of mailboxes: {num_mailboxes}"
if state != State.OK:
yield Result(state=state, notice=notice)
# Create result for number of global messages
warn, crit = levels_num_global_messages
state = get_state_upper((warn, crit), num_global_messages)
notice = f"Number of messages: {num_global_messages}"
if state != State.OK:
yield Result(state=state, notice=notice)
# create the new agent section, must begin with "agent_section_"
# and must be an instance of "AgentSection"
agent_section_mailcow_info = AgentSection(
# "name" must exactly match the section name within the agent output
name="mailcow_info",
# define the parse function, name is arbitrary, a good choice is to choose
# "parse_" as prefix and append the section name
parse_function=parse_mailcow_info,
)
# create the new check plugin, must begin with "check_plugin_"
# and must be an instance of "CheckPlugin"
check_plugin_mailcow_info = CheckPlugin(
# "name" should be the same as the corresponding section within the agent output
name="mailcow_info",
service_name="Mailcow info",
# define the discovery function, name is arbitrary, a good choice is to choose
# "discover_" as prefix and append the section name
discovery_function=discover_mailcow_info,
# define the check function, name is arbitrary, a good choice is to choose
# "check_" as prefix and append the section name
check_function=check_mailcow_info,
# define the default parameters
check_default_parameters={
"levels_num_domains": ("fixed", (100, 200)),
"levels_num_mailboxes": ("fixed", (500, 1000)),
"levels_num_global_messages": ("fixed", (100_000, 250_000)),
},
# connect to the ruleset where parameters can be defined
# must match the name of the ruleset exactly
check_ruleset_name="mailcow_info",
)

View File

@ -0,0 +1,211 @@
#!/usr/bin/env python3
# pylint: disable=too-many-locals, line-too-long, too-many-statements, too-many-branches
"""Mailcow check for mailboxes"""
import time
# import necessary elements from API version 2
from cmk.agent_based.v2 import (
AgentSection,
CheckPlugin,
Service,
Result,
State,
Metric,
render,
)
def get_state_upper(
levels: tuple[int | float, int | float], value: int | float
) -> State:
"""returns OK/WARN/CRIT depending on the given parameters"""
warn, crit = levels
if value >= crit:
return State.CRIT
if value >= warn:
return State.WARN
return State.OK
def parse_mailcow_mailboxes(string_table):
"""the parse function"""
parsed_data = {}
# convert the raw output of the agent section to an meaningful structure
# do type conversions and so on
for line in string_table:
mailboxname = line[0]
value_active = int(line[1])
if value_active == 1:
active = "yes"
else:
active = "no"
# calculate creation and last modification date in human readable format
create_time_value = line[2]
if create_time_value == "None":
create_time_data = "Not available"
else:
create_time_data = create_time_value
modify_time_value = line[3]
if modify_time_value == "None":
modify_time_data = "Never"
else:
modify_time_data = modify_time_value
# get display name
display_name = line[4]
# number of messages within mailbox
number_of_messages = int(line[5])
# calculate storage used for all messages in mailbox
quota = int(line[7])
total_number_of_bytes_used = int(line[8])
if quota == 0:
# quota is not set, if this is the case, line[6] contains no numeric value, but the char "-"
# so set all usage counters to zero
percent_in_use = 0
percent_storage_used_for_messages = 0
else:
# percent in use, rounded to full percent (calculated by Mailcow)
percent_in_use = int(line[6])
# let's calculate our own value
percent_storage_used_for_messages = total_number_of_bytes_used * 100 / quota
# get time of last login for IMAP/POP3/SMTP (seconds since epoch)
last_imap_login = int(line[9])
last_pop3_login = int(line[10])
last_smtp_login = int(line[11])
# transfer these times into a human friendly format
if last_imap_login == 0:
last_imap_login_data = "Never"
else:
curr_time = int(time.time())
diff_time = curr_time - last_imap_login
last_imap_login_data = render.timespan(diff_time)
if last_pop3_login == 0:
last_pop3_login_data = "Never"
else:
curr_time = int(time.time())
diff_time = curr_time - last_pop3_login
last_pop3_login_data = render.timespan(diff_time)
if last_smtp_login == 0:
last_smtp_login_data = "Never"
else:
curr_time = int(time.time())
diff_time = curr_time - last_smtp_login
last_smtp_login_data = render.timespan(diff_time)
# store all (calculated) data
parsed_data[f"{mailboxname}"] = [
active,
create_time_data,
modify_time_data,
display_name,
number_of_messages,
percent_in_use,
quota,
total_number_of_bytes_used,
percent_storage_used_for_messages,
last_imap_login_data,
last_pop3_login_data,
last_smtp_login_data,
]
return parsed_data
def discover_mailcow_mailboxes(section):
"""the discover function"""
# since we have a service with item here we must create one service per item
for key in section:
yield Service(item=key)
def check_mailcow_mailboxes(item, params, section):
"""the check function"""
mailbox = section.get(item)
if not mailbox:
# if a previously found domain does not exist anymore, create a meaningful result
yield Result(
state=State.UNKNOWN,
summary="Mailbox not found anymore, maybe it was deleted?",
)
return
# get all infos regarding the mailbox
active = mailbox[0]
create_time = mailbox[1]
modify_time = mailbox[2]
display_name = mailbox[3]
number_of_messages = mailbox[4]
_percent_in_use = mailbox[5]
quota = mailbox[6]
total_number_of_bytes_used = mailbox[7]
percent_storage_used_for_messages = mailbox[8]
last_imap_login_data = mailbox[9]
last_pop3_login_data = mailbox[10]
last_smtp_login_data = mailbox[11]
# create (main) service for used storage (mailbox quota)
_type, levels = params["levels_mailcow_mailboxes_quota_used"]
state_quota = get_state_upper(levels, percent_storage_used_for_messages)
# create graph for used quota
yield Metric(
"mailcow_mailboxes_used_quota", percent_storage_used_for_messages, levels=levels
)
summary_quota = f"Storage quota for mailbox of '{display_name}' is {render.percent(percent_storage_used_for_messages)}"
details_quota = f"Quota: {render.bytes(total_number_of_bytes_used)} of {render.bytes(quota)} used"
# create service
yield Result(state=state_quota, summary=summary_quota, details=details_quota)
# create some additional services and information only details
notice = f"Active: {active}"
yield Result(state=State.OK, notice=notice)
notice = f"Creation date: {create_time}"
yield Result(state=State.OK, notice=notice)
notice = f"Last modified: {modify_time}"
yield Result(state=State.OK, notice=notice)
notice = f"Last IMAP login: {last_imap_login_data} ago"
yield Result(state=State.OK, notice=notice)
notice = f"Last POP3 login: {last_pop3_login_data} ago"
yield Result(state=State.OK, notice=notice)
notice = f"Last SMTP login: {last_smtp_login_data} ago"
yield Result(state=State.OK, notice=notice)
# create service for number of messages
_type, levels = params["levels_mailcow_mailboxes_num_messages"]
state_messages = get_state_upper(levels, number_of_messages)
yield Metric("mailcow_mailboxes_messages", number_of_messages, levels=levels)
notice = f"Number of messages: {number_of_messages}"
yield Result(state=state_messages, notice=notice)
# create the new agent section, must begin with "agent_section_"
# and must be an instance of "AgentSection"
agent_section_mailcow_mailboxes = AgentSection(
# "name" must exactly match the section name within the agent output
name="mailcow_mailboxes",
# define the parse function, name is arbitrary, a good choice is to choose
# "parse_" as prefix and append the section name
parse_function=parse_mailcow_mailboxes,
)
# create the new check plugin, must begin with "check_plugin_"
# and must be an instance of "CheckPlugin"
check_plugin_mailcow_mailboxes = CheckPlugin(
# "name" should be the same as the corresponding section within the agent output
name="mailcow_mailboxes",
# this is a service with item, so you have to include a place holder for the item id
service_name="Mailcow mailbox %s",
# define the discovery function, name is arbitrary, a good choice is to choose
# "discover_" as prefix and append the section name
discovery_function=discover_mailcow_mailboxes,
# define the check function, name is arbitrary, a good choice is to choose
# "check_" as prefix and append the section name
check_function=check_mailcow_mailboxes,
# define the default parameters
check_default_parameters={
"levels_mailcow_mailboxes_quota_used": ("fixed", (65.0, 85.0)),
"levels_mailcow_mailboxes_num_messages": ("fixed", (1000, 2500)),
},
# connect to the ruleset where parameters can be defined
# must match the name of the ruleset exactly
check_ruleset_name="mailcow_mailboxes",
)

View File

@ -17,4 +17,4 @@ description:
item: item:
domainname domainname
inventory: inventory:
one service is created for each domain one service is created for each email domain

View File

@ -0,0 +1,112 @@
#!/usr/bin/env python3
# pylint: disable=unused-import
"""graphing and metrics definitions"""
from cmk.graphing.v1 import Title
from cmk.graphing.v1.graphs import Graph, MinimalRange
from cmk.graphing.v1.metrics import Color, DecimalNotation, Metric, Unit
from cmk.graphing.v1.perfometers import Closed, FocusRange, Perfometer
## Metrics
# metrics must begin with "metric_" and be an instance of "Metric"
# Global metrics
metric_mc_num_domains = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mc_num_domains",
title=Title("Number of email domains"),
unit=Unit(DecimalNotation("")),
color=Color.DARK_ORANGE,
)
metric_mc_num_global_messages = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mc_num_global_messages",
title=Title("Global number of messages"),
unit=Unit(DecimalNotation("")),
color=Color.DARK_CYAN,
)
metric_mc_num_mailboxes = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mc_num_mailboxes",
title=Title("Number of mailboxes"),
unit=Unit(DecimalNotation("")),
color=Color.DARK_GREEN,
)
# Mailbox Metrics
metric_mc_mailboxes_used_quota = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mailcow_mailboxes_used_quota",
title=Title("Percentage of mailbox quota used"),
unit=Unit(DecimalNotation("%")),
color=Color.DARK_ORANGE,
)
metric_mc_mailboxes_messages = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mailcow_mailboxes_messages",
title=Title("Number of messages in mailbox"),
unit=Unit(DecimalNotation("")),
color=Color.DARK_RED,
)
# Domain metrics
metric_mc_domains_used_quota = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mailcow_domains_used_quota",
title=Title("Percentage of domain quota used"),
unit=Unit(DecimalNotation("%")),
color=Color.DARK_ORANGE,
)
metric_mc_domains_mailboxes = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mailcow_domains_mailboxes",
title=Title("Percentage of maximum mailboxes used"),
unit=Unit(DecimalNotation("%")),
color=Color.DARK_GREEN,
)
metric_mc_domains_aliases = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mailcow_domains_aliases",
title=Title("Percentage of maximum aliases used"),
unit=Unit(DecimalNotation("%")),
color=Color.DARK_BLUE,
)
metric_mc_domains_messages = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mailcow_domains_messages",
title=Title("Number of messages"),
unit=Unit(DecimalNotation("")),
color=Color.DARK_RED,
)
metric_mc_domains_configured_aliases = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mailcow_domains_configured_aliases",
title=Title("Number of configured aliases"),
unit=Unit(DecimalNotation("")),
color=Color.DARK_CYAN,
)
metric_mc_domains_configured_mailboxes = Metric(
# "name" must be exactly the "metric_name" within the check function
name="mailcow_domains_configured_mailboxes",
title=Title("Number of configured mailboxes"),
unit=Unit(DecimalNotation("")),
color=Color.DARK_PINK,
)
## Perfometers
# perfometers must begin with "perfometer_" and be an instance of "Perfometer"
perfometer_mc_domains_used_quota = Perfometer(
name="mailcow_domains_used_quota",
focus_range=FocusRange(Closed(0), Closed(100)),
# "segments" must be exactly the name of the metric
segments=["mailcow_domains_used_quota"],
)
perfometer_mc_mailboxes_used_quota = Perfometer(
name="mailcow_mailboxes_used_quota",
focus_range=FocusRange(Closed(0), Closed(100)),
# "segments" must be exactly the name of the metric
segments=["mailcow_mailboxes_used_quota"],
)

View File

@ -1,206 +1,70 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# pylint: disable=line-too-long, too-many-branches, too-many-locals
# pylint: disable=bare-except, pointless-string-statement, consider-using-dict-items
# pylint: disable=too-many-arguments, too-many-statements, simplifiable-if-statement, too-many-positional-arguments
# pylint: disable=possibly-used-before-assignment, used-before-assignment
# pylint: disable=missing-class-docstring, missing-module-docstring, missing-function-docstring
"""CheckMK special agent for Mailcow systems"""
import getopt import json
import argparse
import sys import sys
import requests import requests
import urllib3 import urllib3
import json
import os
import cmk.utils.password_store
from pprint import pprint
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from requests.auth import HTTPBasicAuth
from time import (
time,
localtime,
strftime,
)
def showUsage() -> None: def get_args() -> argparse.Namespace:
sys.stderr.write( parser: argparse.ArgumentParser = argparse.ArgumentParser(
"""CheckMK Mailcow Special Agent description="Mailcow server parameters"
USAGE: agent_nextcloud -H [hostname] -k [apikey]
agent_nextcloud -h
OPTIONS:
-H, --hostname Hostname (FQDN or IP) of Nextcloud server
-k, --apikey API Key
-P, --port Port
--check-version True|False If "True": Running version will be checked against official Github repo
--no-https True|False If "True": Disable HTTPS, use HTTP (not recommended!)
--no-cert-check True|False If "True": Disable TLS certificate check (not recommended!)
-h, --help Show this help message and exit
"""
) )
parser.add_argument(
"--hostname", required=True, type=str, help="Hostname or IP of mailcow server"
# set this to True to produce debug output (this clutters the agent output)
# be aware: activating this logs very sensitive information to debug files in ~/tmp
# !!DO NOT FORGET to delete these files after debugging is done!!
DEBUG = False
mc_api_base = "api/v1/get"
opt_hostname = ""
opt_apikey = ""
opt_port = ""
opt_check_version = False
opt_no_https = False
opt_no_cert_check = False
short_options = "hH:k:P:"
long_options = [
"hostname=",
"apikey=",
"port=",
"check-version=",
"no-https=",
"no-cert-check=",
"help",
]
domain_data = {}
mailbox_data = {}
debugLogFilename = ""
SEP = "|"
# SEP = "\t"
TIMEFMT = "%Y-%m-%d %H:%M:%S"
FLOATFMT = "{:.4f}"
"""
Decorator function for debugging purposes
creates a file with many information regarding the function call, like:
timestamp
name of function
runtime
number of arguments
number of keyword arguments
return value of function call
type of return value
all parameters given to function
"""
def debugLog(function):
def wrapper(*args, **kwargs):
# execute function and measure runtime
start = time()
value = function(*args, **kwargs)
end = time()
# get number of args and kwargs
len_args = len(args)
len_kwargs = len(kwargs)
# format the output
seconds = FLOATFMT.format(end - start)
local_time = strftime(TIMEFMT, localtime(start))
# get function name
fname = function.__name__
# create output
# out1: Timestamp|Name of Function|Runtime|Num Args|Num Kwargs|Return Value|Return Value Type
out1 = f"{local_time}{SEP}{fname}{SEP}{seconds}{SEP}{len_args}{SEP}{len_kwargs}{SEP}{value}{SEP}{type(value)}"
# out2: all arguments
out2 = ""
for arg in args:
out2 = out2 + SEP + str(arg)
# out3: all keyword arguments
out3 = ""
if len_kwargs > 0:
for key, val in kwargs.items():
out3 = out3 + SEP + key + ":" + str(val)
# write output to file
if debugLogFilename != "":
try:
with open(debugLogFilename, "a+") as f:
f.write(f"{out1}{out2}{out3}\n")
except:
sys.stderr.write(
f"Something went wrong when writing to file {debugLogFilename}\n"
) )
sys.exit(1) parser.add_argument("--apikey", required=True, type=str, help="API key")
else: parser.add_argument(
sys.stderr.write(f"Debug activated, but no debug filename given.\n") "--port",
sys.exit(1) required=False,
return value type=int,
help="Port where the server is listening on",
return wrapper
def getDebugFilename(hostname: str) -> str:
home_path = os.getenv("HOME")
tmp_path = f"{home_path}/tmp"
file_name = f"{tmp_path}/mailcow_{hostname}_debug.log"
return file_name
def getOptions() -> None:
global opt_hostname
global opt_apikey
global opt_port
global opt_check_version
global opt_no_https
global opt_no_cert_check
opts, args = getopt.getopt(sys.argv[1:], short_options, long_options)
for opt, arg in opts:
if opt in ["-H", "--hostname"]:
opt_hostname = arg
elif opt in ["-k", "--apikey"]:
opt_apikey = arg
elif opt in ["-P", "--port"]:
opt_port = arg
elif opt in ["--check-version"]:
if arg == "True":
opt_check_version = True
else:
opt_check_version = False
elif opt in ["--no-https"]:
if arg == "True":
opt_no_https = True
else:
opt_no_https = False
elif opt in ["--no-cert-check"]:
if arg == "True":
opt_no_cert_check = True
else:
opt_no_cert_check = False
elif opt in ["-h", "--help"]:
showUsage()
sys.exit(0)
if DEBUG:
home_path = os.getenv("HOME")
tmp_path = f"{home_path}/tmp"
help_file = f"{tmp_path}/mailcow_{opt_hostname}_debug.txt"
with open(help_file, "a") as file:
file.write(
f"Number of Arguments: {len(sys.argv)}, Argument List: {str(sys.argv)}\n"
) )
parser.add_argument(
"--no-https",
# @debugLog required=False,
def showOptions() -> None: default=False,
print(f"Hostname: {opt_hostname}") help="Disable HTTPS",
print(f"Username: {opt_apikey}") action="store_true",
print(f"Port: {opt_port}")
print(f"Check Version: {opt_check_version}")
print(f"No HTTPS: {opt_no_https}")
print(f"No TLS Check: {opt_no_cert_check}")
home_path = os.getenv("HOME")
tmp_path = f"{home_path}/tmp"
help_file = f"{tmp_path}/mailcow_{opt_hostname}_debug.txt"
with open(help_file, "a") as file:
file.write(
f"Hostname: {opt_hostname}, Port: {opt_port}, No HTTPS: {opt_no_https}, No Cert Check: {opt_no_cert_check}\n"
) )
parser.add_argument(
"--no-cert-check",
required=False,
default=False,
help="Disable certificate checks",
action="store_true",
)
parser.add_argument(
"--check-version",
required=False,
default=False,
help="Enable version check",
action="store_true",
)
args = parser.parse_args()
return args
# @debugLog MC_API_BASE: str = "api/v1/get"
def getDomainInfo(headers: str, verify: bool, base_url: str) -> int: TIMEOUT: int = 30
url = f"{base_url}/domain/all"
response = requests.get(url, headers=headers, verify=verify) domain_data: dict = {}
mailbox_data: dict = {}
def get_domain_info(headers: str, verify: bool, base_url: str) -> int:
"""retrieves info about all configured domains"""
url: str = f"{base_url}/domain/all"
response = requests.get(url=url, headers=headers, verify=verify, timeout=TIMEOUT)
if response.status_code == 200: if response.status_code == 200:
jsdata = response.text jsdata = response.text
data = json.loads(jsdata) # returns a list of dictionaries data = json.loads(jsdata) # returns a list of dictionaries
@ -243,22 +107,21 @@ def getDomainInfo(headers: str, verify: bool, base_url: str) -> int:
i += 1 i += 1
# return number of email domains # return number of email domains
return i return i
else:
sys.stderr.write( sys.stderr.write(
f"Request response code is {response.status_code} with URL {url}\n" f"Request response code is {response.status_code} with URL {url}\n"
) )
sys.exit(1) sys.exit(1)
# @debugLog def get_mailbox_info(headers: str, verify: bool, base_url: str) -> tuple:
def getMailboxInfo(headers: str, verify: bool, base_url: str) -> tuple: """retrieves info about all configured mailboxes"""
url = f"{base_url}/mailbox/all" url: str = f"{base_url}/mailbox/all"
response = requests.get(url, headers=headers, verify=verify) response = requests.get(url=url, headers=headers, verify=verify, timeout=TIMEOUT)
if response.status_code == 200: if response.status_code == 200:
jsdata = response.text jsdata = response.text
data = json.loads(jsdata) # returns a list of dictionaries data = json.loads(jsdata) # returns a list of dictionaries
i = 0 i: int = 0
global_num_messages = 0 global_num_messages: int = 0
while i < len(data): while i < len(data):
# get status of mailbox (0-->inactive or 1-->active) # get status of mailbox (0-->inactive or 1-->active)
active = data[i].get("active") active = data[i].get("active")
@ -282,7 +145,7 @@ def getMailboxInfo(headers: str, verify: bool, base_url: str) -> tuple:
last_imap_login = data[i].get("last_imap_login") last_imap_login = data[i].get("last_imap_login")
last_pop3_login = data[i].get("last_pop3_login") last_pop3_login = data[i].get("last_pop3_login")
last_smtp_login = data[i].get("last_smtp_login") last_smtp_login = data[i].get("last_smtp_login")
mb = { mailbox = {
"active": active, "active": active,
"created": created, "created": created,
"modified": modified, "modified": modified,
@ -297,27 +160,26 @@ def getMailboxInfo(headers: str, verify: bool, base_url: str) -> tuple:
"last_smtp_login": last_smtp_login, "last_smtp_login": last_smtp_login,
} }
mailbox_data[username] = {} mailbox_data[username] = {}
mailbox_data[username] = mb mailbox_data[username] = mailbox
i += 1 i += 1
global_num_messages += num_messages global_num_messages += num_messages
# return number of mailboxes and global number of messages # return number of mailboxes and global number of messages
return i, global_num_messages return i, global_num_messages
else:
sys.stderr.write( sys.stderr.write(
f"Request response code is {response.status_code} with URL {url}\n" f"Request response code is {response.status_code} with URL {url}\n"
) )
sys.exit(1) sys.exit(1)
def getGitVersion() -> str: def get_git_version() -> str:
url = "https://api.github.com/repos/mailcow/mailcow-dockerized/releases/latest" """gets the actual version of Mailcow from Github"""
git_version = "" url: str = "https://api.github.com/repos/mailcow/mailcow-dockerized/releases/latest"
response = requests.get(url) git_version: str = ""
response = requests.get(url=url, timeout=TIMEOUT)
if response.status_code == 200: if response.status_code == 200:
jsdata = response.text jsdata = response.text
data = json.loads(jsdata) # returns a dictionary data = json.loads(jsdata) # returns a dictionary
git_version = data["tag_name"] git_version = data["tag_name"]
# print(git_version)
else: else:
sys.stderr.write( sys.stderr.write(
f"Request response code is {response.status_code} with URL {url}\n" f"Request response code is {response.status_code} with URL {url}\n"
@ -326,8 +188,9 @@ def getGitVersion() -> str:
return git_version return git_version
def compareVersions(mc_ver: str, git_ver: str) -> bool: def compare_versions(mc_ver: str, git_ver: str) -> bool:
update_available = False """compares the actual Github version with the version of the running Mailcow system"""
update_available: bool = False
try: try:
mc_year, mc_month = mc_ver.split("-") mc_year, mc_month = mc_ver.split("-")
git_year, git_month = git_ver.split("-") git_year, git_month = git_ver.split("-")
@ -360,15 +223,18 @@ def compareVersions(mc_ver: str, git_ver: str) -> bool:
elif git_month_ver > mc_month_ver: elif git_month_ver > mc_month_ver:
update_available = True update_available = True
except: except:
# TBD
pass pass
return update_available return update_available
# @debugLog def get_mailcow_info(
def getMailcowInfo(headers: str, verify: bool, base_url: str) -> dict: headers: str, verify: bool, base_url: str, check_version: bool
) -> dict:
"""retrieves several global information about the mailcow system"""
info_data = {} info_data = {}
url = f"{base_url}/status/version" url = f"{base_url}/status/version"
response = requests.get(url, headers=headers, verify=verify) response = requests.get(url=url, headers=headers, verify=verify, timeout=TIMEOUT)
if response.status_code == 200: if response.status_code == 200:
jsdata = response.text jsdata = response.text
data = json.loads(jsdata) # returns a dictionary data = json.loads(jsdata) # returns a dictionary
@ -376,20 +242,17 @@ def getMailcowInfo(headers: str, verify: bool, base_url: str) -> dict:
# get Mailcow version # get Mailcow version
mc_version = data["version"] mc_version = data["version"]
# if enabled, check this version against the official Mailcow repo on Github # if enabled, check this version against the official Mailcow repo on Github
if opt_check_version: if check_version:
git_version = getGitVersion() git_version = get_git_version()
update_available = compareVersions(mc_version, git_version) update_available = compare_versions(mc_version, git_version)
# update_available = compareVersions("2023-01a", "2023-02")
# print(update_available)
info_data["git_version"] = git_version info_data["git_version"] = git_version
info_data["update_available"] = update_available info_data["update_available"] = update_available
else: else:
info_data["git_version"] = "Version check disabled" info_data["git_version"] = "Version check disabled"
info_data["update_available"] = False info_data["update_available"] = False
info_data["mc_version"] = mc_version info_data["mc_version"] = mc_version
info_data["check_version_enabled"] = opt_check_version info_data["check_version_enabled"] = check_version
return info_data return info_data
else:
sys.stderr.write( sys.stderr.write(
f"Request response code is {response.status_code} with URL {url}\n" f"Request response code is {response.status_code} with URL {url}\n"
) )
@ -418,8 +281,8 @@ user1@dom2.de;1;2022-04-30 09:55:37;2022-04-30 09:55:37;Melissa;53460;33;1996488
""" """
# @debugLog def do_cmk_output_mailboxes() -> None:
def doCmkOutputMailboxes() -> None: """prints out agent section for mailboxes"""
print("<<<mailcow_mailboxes:sep(59)>>>") print("<<<mailcow_mailboxes:sep(59)>>>")
for mb in mailbox_data: for mb in mailbox_data:
active = mailbox_data[mb]["active"] active = mailbox_data[mb]["active"]
@ -440,8 +303,7 @@ def doCmkOutputMailboxes() -> None:
) )
# @debugLog def do_cmk_output_mailcow(
def doCmkOutputMailcow(
version: str, version: str,
num_domains: int, num_domains: int,
num_mailboxes: int, num_mailboxes: int,
@ -450,6 +312,7 @@ def doCmkOutputMailcow(
update_available: bool, update_available: bool,
check_version_enabled: bool, check_version_enabled: bool,
) -> None: ) -> None:
"""prints out agent section for global mailcow info"""
print("<<<mailcow_info:sep(59)>>>") print("<<<mailcow_info:sep(59)>>>")
# strip semicolons, if present, since we use it as delimiter # strip semicolons, if present, since we use it as delimiter
version = version.replace(";", "") version = version.replace(";", "")
@ -460,11 +323,11 @@ def doCmkOutputMailcow(
""" """
Output is as follows: Output is as follows:
0 domain_name 0 domain name
1 active 1 --> active, 0 --> not active 1 active 1 --> active, 0 --> not active
2 creation date "None" if ??? 2 creation date "None" if ???
3 last modified date "None" if never modified 3 last modified date "None" if never modified
4 max number mailboxes 4 max number of mailboxes
5 number of mailboxes 5 number of mailboxes
6 max number of aliases 6 max number of aliases
7 number of aliases 7 number of aliases
@ -479,8 +342,8 @@ dom3.de;1;2022-04-29 13:36:08;2022-04-29 21:26:19;10;1;100;3;28132;12852485367;2
""" """
# @debugLog def do_cmk_output_domains() -> None:
def doCmkOutputDomains() -> None: """print out agent section for domains"""
print("<<<mailcow_domains:sep(59)>>>") print("<<<mailcow_domains:sep(59)>>>")
for dom in domain_data: for dom in domain_data:
active = domain_data[dom]["active"] active = domain_data[dom]["active"]
@ -499,74 +362,77 @@ def doCmkOutputDomains() -> None:
def main(): def main():
global debugLogFilename """main function"""
cmk.utils.password_store.replace_passwords() # get and check parameters
getOptions() params: argparse.Namespace = get_args()
# do some parameter checks # do some parameter checks
if opt_hostname == "": if params.hostname is None:
sys.stderr.write(f"No hostname given.\n") sys.stderr.write("No hostname given.\n")
showUsage()
sys.exit(1) sys.exit(1)
else: else:
hostname = opt_hostname hostname = params.hostname
if opt_apikey == "": if params.apikey is None:
sys.stderr.write(f"No API key given.\n") sys.stderr.write("No API key given.\n")
showUsage()
sys.exit(1) sys.exit(1)
if opt_no_cert_check: else:
apikey = params.apikey
if params.no_cert_check:
# disable certificate warnings # disable certificate warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
verify = False verify = False
else: else:
verify = True verify = True
if opt_port == "": if params.check_version:
if opt_no_https: check_version = True
else:
check_version = False
if params.port is None:
if params.no_https:
protocol = "http" protocol = "http"
port = "80" port = "80"
else: else:
protocol = "https" protocol = "https"
port = "443" port = "443"
else: else:
if opt_no_https: if params.no_https:
protocol = "http" protocol = "http"
else: else:
protocol = "https" protocol = "https"
port = opt_port port = params.port
if protocol == "http" and port == "443": if protocol == "http" and port == "443":
sys.stderr.write(f"Combining HTTP with port 443 is not supported.\n") sys.stderr.write("Combining HTTP with port 443 is not supported.\n")
sys.exit(1) sys.exit(1)
if protocol == "https" and port == "80": if protocol == "https" and port == "80":
sys.stderr.write(f"Combining HTTPS with port 80 is not supported.\n") sys.stderr.write("Combining HTTPS with port 80 is not supported.\n")
sys.exit(1) sys.exit(1)
headers = CaseInsensitiveDict() headers = CaseInsensitiveDict()
headers["Accept"] = "application/json" headers["Accept"] = "application/json"
headers["X-API-Key"] = opt_apikey headers["X-API-Key"] = apikey
# now "hostname" contains the FQDN of the host running Mailcow # now "hostname" contains the FQDN of the host running Mailcow
# now "protocol" is http or https # now "protocol" is http or https
# now "port" contains the port number to use # now "port" contains the port number to use
# now "verify" signals whether certificate checking will be done (True) or not (False) # now "verify" signals whether certificate checking will be done (True) or not (False)
# now "check_version" signals whether the Mailcow version will be checked against the Github version
# now "headers" contains the accepted format (JSON) and the API key to use # now "headers" contains the accepted format (JSON) and the API key to use
debugLogFilename = getDebugFilename(hostname) base_url = f"{protocol}://{hostname}:{port}/{MC_API_BASE}"
if DEBUG:
showOptions()
print(
f"hostname: {hostname}, protocol: {protocol}, port: {port}, verify: {verify}"
)
base_url = f"{protocol}://{hostname}:{port}/{mc_api_base}"
# get domain data # get domain data
num_domains = getDomainInfo(headers, verify, base_url) num_domains = get_domain_info(headers=headers, verify=verify, base_url=base_url)
# get mailbox data # get mailbox data
num_mailboxes, num_global_messages = getMailboxInfo(headers, verify, base_url) num_mailboxes, num_global_messages = get_mailbox_info(
headers=headers, verify=verify, base_url=base_url
)
# get global Mailcow info # get global Mailcow info
mailcow_info = getMailcowInfo(headers, verify, base_url) mailcow_info = get_mailcow_info(
headers=headers, verify=verify, base_url=base_url, check_version=check_version
)
mailcow_version = mailcow_info["mc_version"] mailcow_version = mailcow_info["mc_version"]
github_version = mailcow_info["git_version"] github_version = mailcow_info["git_version"]
update_available = mailcow_info["update_available"] update_available = mailcow_info["update_available"]
check_version_enabled = mailcow_info["check_version_enabled"] check_version_enabled = mailcow_info["check_version_enabled"]
# create agent output # create agent output
doCmkOutputDomains() do_cmk_output_domains()
doCmkOutputMailboxes() do_cmk_output_mailboxes()
doCmkOutputMailcow( do_cmk_output_mailcow(
mailcow_version, mailcow_version,
num_domains, num_domains,
num_mailboxes, num_mailboxes,

View File

@ -0,0 +1,93 @@
#!/user/bin/env python3
"""parameter form ruleset for mailcow domains"""
from cmk.rulesets.v1 import Title
from cmk.rulesets.v1.form_specs import (
DefaultValue,
DictElement,
Dictionary,
LevelDirection,
SimpleLevels,
Percentage,
Integer,
)
from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic
# function name should begin with an underscore to limit it's visibility
def _parameter_form():
return Dictionary(
elements={
"levels_mailcow_domains_quota_used": DictElement(
parameter_form=SimpleLevels(
title=Title("Mailcow domains quota usage for storage"),
form_spec_template=Percentage(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(65.0, 85.0)),
),
required=True,
),
"levels_mailcow_domains_mailboxes_used": DictElement(
parameter_form=SimpleLevels(
title=Title("Mailcow domains mailboxes usage"),
form_spec_template=Percentage(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(65.0, 85.0)),
),
required=True,
),
"levels_mailcow_domains_aliases_used": DictElement(
parameter_form=SimpleLevels(
title=Title("Mailcow domains aliases usage"),
form_spec_template=Percentage(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(65.0, 85.0)),
),
required=True,
),
"levels_mailcow_domains_num_messages": DictElement(
parameter_form=SimpleLevels(
title=Title("Number of messages"),
form_spec_template=Integer(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(10_000, 25_000)),
),
required=True,
),
"levels_mailcow_domains_num_aliases": DictElement(
parameter_form=SimpleLevels(
title=Title("Number of configured aliases"),
form_spec_template=Integer(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(100, 250)),
),
required=True,
),
"levels_mailcow_domains_num_mailboxes": DictElement(
parameter_form=SimpleLevels(
title=Title("Number of configured mailboxes"),
form_spec_template=Integer(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(100, 250)),
),
required=True,
),
}
)
# name must begin with "rule_spec_", should refer to the used check plugin
# must be an instance of "CheckParameters"
rule_spec_mailcow_domains = CheckParameters(
# "name" should be the same as the check plugin
name="mailcow_domains",
# the title is shown in the GUI
title=Title("Mailcow domain levels"),
# this ruleset can be found under Setup|Service monitoring rules|Various
topic=Topic.APPLICATIONS,
# define the name of the function which creates the GUI elements
parameter_form=_parameter_form,
# define the label in the GUI where you can restrict the
# settings to one or mor specific users (item)
condition=HostAndItemCondition(item_title=Title("Domain")),
)

View File

@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""
defines the form for typing in all needed levels
regarding number of domains, mailboxes and global message count
"""
from cmk.rulesets.v1 import Help, Title
from cmk.rulesets.v1.form_specs import (
DictElement,
Dictionary,
SimpleLevels,
LevelDirection,
DefaultValue,
Integer,
)
from cmk.rulesets.v1.rule_specs import (
CheckParameters,
Topic,
HostCondition,
)
def _parameter_form() -> Dictionary:
return Dictionary(
title=Title("Levels for global Mailcow information"),
help_text=Help("Checking Mailcow systems via API"),
elements={
"levels_num_domains": DictElement(
parameter_form=SimpleLevels(
title=Title("Levels for number of email domains"),
form_spec_template=Integer(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(100, 200)),
),
required=True,
),
"levels_num_mailboxes": DictElement(
parameter_form=SimpleLevels(
title=Title("Levels for number of mailboxes"),
form_spec_template=Integer(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(500, 1000)),
),
required=True,
),
"levels_num_global_messages": DictElement(
parameter_form=SimpleLevels(
title=Title("Levels for number of messages"),
form_spec_template=Integer(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(100_000, 250_000)),
),
required=True,
),
},
)
rule_spec_mailcow_info = CheckParameters(
name="mailcow_info",
title=Title("Mailcow global levels"),
topic=Topic.APPLICATIONS,
parameter_form=_parameter_form,
condition=HostCondition(),
)

View File

@ -0,0 +1,57 @@
#!/user/bin/env python3
"""parameter form ruleset for mailcow domains"""
from cmk.rulesets.v1 import Title
from cmk.rulesets.v1.form_specs import (
DefaultValue,
DictElement,
Dictionary,
LevelDirection,
SimpleLevels,
Percentage,
Integer,
)
from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic
# function name should begin with an underscore to limit it's visibility
def _parameter_form():
return Dictionary(
elements={
"levels_mailcow_mailboxes_quota_used": DictElement(
parameter_form=SimpleLevels(
title=Title("Mailcow mailbox quota usage for storage"),
form_spec_template=Percentage(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(65.0, 85.0)),
),
required=True,
),
"levels_mailcow_mailboxes_num_messages": DictElement(
parameter_form=SimpleLevels(
title=Title("Number of messages in mailbox"),
form_spec_template=Integer(),
level_direction=LevelDirection.UPPER,
prefill_fixed_levels=DefaultValue(value=(1000, 2500)),
),
required=True,
),
}
)
# name must begin with "rule_spec_", should refer to the used check plugin
# must be an instance of "CheckParameters"
rule_spec_mailcow_mailboxes = CheckParameters(
# "name" should be the same as the check plugin
name="mailcow_mailboxes",
# the title is shown in the GUI
title=Title("Mailcow mailbox levels"),
# this ruleset can be found under Setup|Service monitoring rules|Various
topic=Topic.APPLICATIONS,
# define the name of the function which creates the GUI elements
parameter_form=_parameter_form,
# define the label in the GUI where you can restrict the
# settings to one or mor specific users (item)
condition=HostAndItemCondition(item_title=Title("Mailbox")),
)

View File

@ -0,0 +1,98 @@
#!/usr/bin/env python3
# pylint: disable=line-too-long
"""defines the form for typing in all needed HAL9002 parameters"""
from cmk.rulesets.v1 import Help, Title, Label
from cmk.rulesets.v1.form_specs import (
DictElement,
Dictionary,
String,
validators,
BooleanChoice,
Integer,
Password,
migrate_to_password,
InputHint,
DefaultValue,
)
from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic
def _form_spec_special_agent_mailcow() -> Dictionary:
return Dictionary(
title=Title("Mailcow Server Information"),
help_text=Help("Checking Mailcow systems via API"),
elements={
"hostname": DictElement(
required=True,
parameter_form=String(
title=Title("Hostname"),
help_text=Help(
"Hostname of Mailcow server (bare FQDN or IP), mandatory, eg. mailcow.yourdomain.tld"
),
custom_validate=(validators.LengthInRange(min_value=1),),
prefill=InputHint("mailcow.yourdomain.tld"),
),
),
"apikey": DictElement(
required=True,
parameter_form=Password(
title=Title("API key"),
help_text=Help("Specify API key, mandatory"),
custom_validate=(validators.LengthInRange(min_value=1),),
migrate=migrate_to_password,
),
),
"port": DictElement(
required=False,
parameter_form=Integer(
title=Title("Port"),
help_text=Help(
"Specify port the Mailcow system ist listening on, mandatory"
),
prefill=DefaultValue(443),
custom_validate=(validators.NetworkPort(),),
),
),
"check_version": DictElement(
required=False,
parameter_form=BooleanChoice(
title=Title("Check Mailcow version"),
help_text=Help(
"Activate to check version of running Mailcow against actual Github version, optional"
),
label=Label(
"Enable version check (requires access to Github internet site)"
),
),
),
"no_https": DictElement(
required=False,
parameter_form=BooleanChoice(
title=Title("Disable HTTPS"),
help_text=Help(
"Activate to disable encryption (not recommended), optional"
),
label=Label("Disable HTTPS"),
),
),
"no_cert_check": DictElement(
required=False,
parameter_form=BooleanChoice(
title=Title("Disable certificate validation"),
help_text=Help(
"Activate to disable certificate validation (not recommended), optional"
),
label=Label("Disable certificate validation"),
),
),
},
)
rule_spec_mailcow = SpecialAgent(
name="mailcow",
title=Title("Mailcow connection parameters"),
topic=Topic.APPLICATIONS,
parameter_form=_form_spec_special_agent_mailcow,
)

View File

@ -0,0 +1,48 @@
#!/usr/bin/env python3
# pylint: disable=missing-class-docstring, missing-module-docstring, missing-function-docstring
from collections.abc import Iterable
from pydantic import BaseModel
from cmk.server_side_calls.v1 import (
HostConfig,
Secret,
SpecialAgentCommand,
SpecialAgentConfig,
)
class MailcowParams(BaseModel):
hostname: str | None = None
apikey: Secret | None = None
port: int | None = None
no_https: bool = False
no_cert_check: bool = False
check_version: bool = False
def agent_mailcow_arguments(
params: MailcowParams, _host_config: HostConfig
) -> Iterable[SpecialAgentCommand]:
command_arguments: list[str | Secret] = []
if params.hostname is not None:
command_arguments += ["--hostname", params.hostname]
if params.apikey is not None:
command_arguments += ["--apikey", params.apikey.unsafe()]
if params.port is not None:
command_arguments += ["--port", str(params.port)]
if params.no_https:
command_arguments.append("--no-https")
if params.no_cert_check:
command_arguments.append("--no-cert-check")
if params.check_version:
command_arguments.append("--check-version")
yield SpecialAgentCommand(command_arguments=command_arguments)
special_agent_mailcow = SpecialAgentConfig(
name="mailcow",
parameter_parser=MailcowParams.model_validate,
commands_function=agent_mailcow_arguments,
)

BIN
mkp/Mailcow-2.1.0.mkp Normal file

Binary file not shown.