initial upload of version 2.1.0 which uses the new plugin API

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

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",
)