diff --git a/README.md b/README.md index bce5cfb..c0fd2eb 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ If the Traefik dashboard reports any critical state, the appropriate service goe Services Overview: -![Services Overview](Traefik-Services-Overview.png) +![Services Overview](images/Traefik-Services-Overview.png) Traefik Info Details: -![Info Details](Traefik-Info-Details.png) +![Info Details](images/Traefik-Info-Details.png) Traefik HTTP Components Details: -![HTTP Components Details](Traefik-HTTPComponents-Details.png) \ No newline at end of file +![HTTP Components Details](images/Traefik-HTTPComponents-Details.png) diff --git a/images/Traefik-HTTPComponents-Details.png b/images/Traefik-HTTPComponents-Details.png new file mode 100644 index 0000000..c95ccf9 Binary files /dev/null and b/images/Traefik-HTTPComponents-Details.png differ diff --git a/images/Traefik-Info-Details.png b/images/Traefik-Info-Details.png new file mode 100644 index 0000000..6626c75 Binary files /dev/null and b/images/Traefik-Info-Details.png differ diff --git a/images/Traefik-Services-Overview.png b/images/Traefik-Services-Overview.png new file mode 100644 index 0000000..8a33dda Binary files /dev/null and b/images/Traefik-Services-Overview.png differ diff --git a/mkp/Traefik-0.1.7.mkp b/mkp/Traefik-0.1.7.mkp new file mode 100644 index 0000000..e364d39 Binary files /dev/null and b/mkp/Traefik-0.1.7.mkp differ diff --git a/traefik/agent_based/traefik_http_components.py b/traefik/agent_based/traefik_http_components.py new file mode 100644 index 0000000..d6d69b6 --- /dev/null +++ b/traefik/agent_based/traefik_http_components.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +# pylint: disable=missing-module-docstring, unused-argument, consider-using-f-string +# pylint: disable=missing-function-docstring, line-too-long + + +# import necessary elements from API version 2 +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + Service, + State, + Metric, + Result, + DiscoveryResult, + CheckResult, + check_levels, +) + + +def parse_traefik_http_components(string_table): + """the parse function""" + parsed_data = {} + for line in string_table: + if line[0] == "routers": + parsed_data["num_routers"] = int(line[1]) + parsed_data["num_routers_warn"] = int(line[2]) + parsed_data["num_routers_crit"] = int(line[3]) + elif line[0] == "services": + parsed_data["num_services"] = int(line[1]) + parsed_data["num_services_warn"] = int(line[2]) + parsed_data["num_services_crit"] = int(line[3]) + elif line[0] == "middlewares": + parsed_data["num_middlewares"] = int(line[1]) + parsed_data["num_middlewares_warn"] = int(line[2]) + parsed_data["num_middlewares_crit"] = int(line[3]) + return parsed_data + + +def discover_traefik_http_components(section) -> DiscoveryResult: + """the discover function""" + yield Service() + + +def check_traefik_http_components(params, section) -> CheckResult: + """the check function""" + _level_type, levels_percent_not_ok = params["levels_traefik_http_components_not_ok"] + levels_min_routers = params["levels_traefik_min_http_routers"] + levels_max_routers = params["levels_traefik_max_http_routers"] + levels_min_services = params["levels_traefik_min_http_services"] + levels_max_services = params["levels_traefik_max_http_services"] + levels_min_middlewares = params["levels_traefik_min_http_middlewares"] + levels_max_middlewares = params["levels_traefik_max_http_middlewares"] + num_routers: int = section["num_routers"] + num_routers_warn: int = section["num_routers_warn"] + num_routers_crit: int = section["num_routers_crit"] + num_services: int = section["num_services"] + num_services_warn: int = section["num_services_warn"] + num_services_crit: int = section["num_services_crit"] + num_middlewares: int = section["num_middlewares"] + num_middlewares_warn: int = section["num_middlewares_warn"] + num_middlewares_crit: int = section["num_middlewares_crit"] + num_components: int = num_routers + num_services + num_middlewares + components_warn: int = num_routers_warn + num_services_warn + num_middlewares_warn + components_crit: int = num_routers_crit + num_services_crit + num_middlewares_crit + components_percent_not_ok: float = 0.0 + if num_components > 0: + components_percent_not_ok = ( + (components_warn + components_crit) * 100 / num_components + ) + yield Metric( + name="traefik_percent_http_components_not_ok", + value=components_percent_not_ok, + levels=levels_percent_not_ok, + ) + summary: str = f"Number of HTTP routers/services/middlewares: {num_routers}/{num_services}/{num_middlewares}" + details_routers: str = ( + f"Routers WARN: {num_routers_warn}\nRouters CRIT: {num_routers_crit}" + ) + details_services: str = ( + f"Services WARN: {num_services_warn}\nServices CRIT: {num_services_crit}" + ) + details_middlewares: str = f"Middlewares WARN: {num_middlewares_warn}\nMiddlewares CRIT: {num_middlewares_crit}\n\n" + details = f"{details_routers}\n\n{details_services}\n\n{details_middlewares}" + state: State = State.OK + if components_warn > 0: + state = State.WARN + if components_crit > 0: + state = State.CRIT + yield Result( + state=state, + summary=summary, + details=details, + ) + yield from check_levels( + metric_name="traefik_num_http_routers", + value=num_routers, + levels_lower=levels_min_routers, + levels_upper=levels_max_routers, + label="Number of HTTP routers", + notice_only=True, + render_func=lambda v: "%.0f" % v, + ) + yield from check_levels( + metric_name="traefik_num_http_services", + value=num_services, + levels_lower=levels_min_services, + levels_upper=levels_max_services, + label="Number of HTTP services", + notice_only=True, + render_func=lambda v: "%.0f" % v, + ) + yield from check_levels( + metric_name="traefik_num_http_middlewares", + value=num_middlewares, + levels_lower=levels_min_middlewares, + levels_upper=levels_max_middlewares, + label="Number of HTTP middlewares", + notice_only=True, + render_func=lambda v: "%.0f" % v, + ) + + +# create the new agent section, must begin with "agent_section_" +# and must be an instance of "AgentSection" +agent_section_traefik_http_components = AgentSection( + # "name" must exactly match the section name within the agent output + name="traefik_http_components", + # define the parse function, name is arbitrary, a good choice is to choose + # "parse_" as prefix and append the section name + parse_function=parse_traefik_http_components, +) + +# create the new check plugin, must begin with "check_plugin_" +# and must be an instance of "CheckPlugin" +check_plugin_traefik_http_components = CheckPlugin( + # "name" should be the same as the corresponding section within the agent output + name="traefik_http_components", + service_name="Traefik HTTP components", + # define the discovery function, name is arbitrary, a good choice is to choose + # "discover_" as prefix and append the section name + discovery_function=discover_traefik_http_components, + # define the check function, name is arbitrary, a good choice is to choose + # "check_" as prefix and append the section name + check_function=check_traefik_http_components, + # define the default parameters + check_default_parameters={ + "levels_traefik_min_http_routers": ("fixed", (10, 5)), + "levels_traefik_max_http_routers": ("fixed", (75, 100)), + "levels_traefik_min_http_services": ("fixed", (10, 5)), + "levels_traefik_max_http_services": ("fixed", (75, 100)), + "levels_traefik_min_http_middlewares": ("fixed", (5, 2)), + "levels_traefik_max_http_middlewares": ("fixed", (20, 50)), + "levels_traefik_http_components_not_ok": ("fixed", (0.5, 1.0)), + }, + # connect to the ruleset where parameters can be defined + # must match the name of the ruleset exactly + check_ruleset_name="traefik_http_components", +) diff --git a/traefik/agent_based/traefik_info.py b/traefik/agent_based/traefik_info.py new file mode 100644 index 0000000..5fdb09c --- /dev/null +++ b/traefik/agent_based/traefik_info.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# pylint: disable=missing-module-docstring, unused-argument, +# pylint: disable=missing-function-docstring, line-too-long + +import datetime +import time + + +# import necessary elements from API version 2 +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + Service, + State, + Metric, + Result, + DiscoveryResult, + CheckResult, + 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_traefik_info(string_table): + """the parse function""" + parsed_data = {} + for line in string_table: + if line[0] == "Traefik_Version": + parsed_data["version"] = line[1] + elif line[0] == "Traefik_CodeName": + parsed_data["codename"] = line[1] + elif line[0] == "Traefik_StartDate": + parsed_data["startdate"] = line[1] + # convert timestamp into seconds since epoch + dt = datetime.datetime.fromisoformat(line[1]) + parsed_data["startdate_seconds_since_epoch"] = int(dt.timestamp()) + elif line[0] == "Agent_Runtime": + # convert seconds into ms + parsed_data["agent_runtime"] = float(line[1]) * 1_000 + elif line[0] == "Agent_Version": + parsed_data["agent_version"] = line[1] + return parsed_data + + +def discover_traefik_info(section) -> DiscoveryResult: + """the discover function""" + yield Service() + + +def check_traefik_info(params, section) -> CheckResult: + """the check function""" + _level_type, levels = params["levels_traefik_agent_execution_time"] + codename: str = section["codename"] + version: str = section["version"] + startdate: str = section["startdate"] + startdate_seconds_since_epoch: int = section["startdate_seconds_since_epoch"] + # calculate runtime of Traefik in seconds + current_epoch_time: int = int(time.time()) + runtime: int = current_epoch_time - startdate_seconds_since_epoch + agent_version: str = section["agent_version"] + agent_runtime: float = round(section["agent_runtime"], 1) + state: State = get_state_upper(levels=levels, value=agent_runtime) + + yield Metric( + name="traefik_agent_execution_time", + value=agent_runtime, + levels=levels, + ) + summary: str = f"Traefik version: {version}, code name: {codename}" + details: str = f"Traefik start date: {startdate}\nRunning since: {render.timespan(runtime)}\n\nAgent version: {agent_version}\nAgent execution time: {agent_runtime}ms" + yield Result(state=state, summary=summary, details=details) + + +# create the new agent section, must begin with "agent_section_" +# and must be an instance of "AgentSection" +agent_section_traefik_info = AgentSection( + # "name" must exactly match the section name within the agent output + name="traefik_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_traefik_info, +) + +# create the new check plugin, must begin with "check_plugin_" +# and must be an instance of "CheckPlugin" +check_plugin_traefik_info = CheckPlugin( + # "name" should be the same as the corresponding section within the agent output + name="traefik_info", + service_name="Traefik 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_traefik_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_traefik_info, + # define the default parameters + check_default_parameters={ + "levels_traefik_agent_execution_time": ("fixed", (500.0, 750.0)) + }, + # connect to the ruleset where parameters can be defined + # must match the name of the ruleset exactly + check_ruleset_name="traefik_info", +) diff --git a/traefik/agent_based/traefik_tcp_components.py b/traefik/agent_based/traefik_tcp_components.py new file mode 100644 index 0000000..c38885d --- /dev/null +++ b/traefik/agent_based/traefik_tcp_components.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +# pylint: disable=missing-module-docstring, unused-argument, consider-using-f-string +# pylint: disable=missing-function-docstring, line-too-long + + +# import necessary elements from API version 2 +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + Service, + State, + Metric, + Result, + DiscoveryResult, + CheckResult, + check_levels, +) + + +def parse_traefik_tcp_components(string_table): + """the parse function""" + parsed_data = {} + for line in string_table: + if line[0] == "routers": + parsed_data["num_routers"] = int(line[1]) + parsed_data["num_routers_warn"] = int(line[2]) + parsed_data["num_routers_crit"] = int(line[3]) + elif line[0] == "services": + parsed_data["num_services"] = int(line[1]) + parsed_data["num_services_warn"] = int(line[2]) + parsed_data["num_services_crit"] = int(line[3]) + elif line[0] == "middlewares": + parsed_data["num_middlewares"] = int(line[1]) + parsed_data["num_middlewares_warn"] = int(line[2]) + parsed_data["num_middlewares_crit"] = int(line[3]) + return parsed_data + + +def discover_traefik_tcp_components(section) -> DiscoveryResult: + """the discover function""" + yield Service() + + +def check_traefik_tcp_components(params, section) -> CheckResult: + """the check function""" + _level_type, levels_percent_not_ok = params["levels_traefik_tcp_components_not_ok"] + levels_min_routers = params["levels_traefik_min_tcp_routers"] + levels_max_routers = params["levels_traefik_max_tcp_routers"] + levels_min_services = params["levels_traefik_min_tcp_services"] + levels_max_services = params["levels_traefik_max_tcp_services"] + levels_min_middlewares = params["levels_traefik_min_tcp_middlewares"] + levels_max_middlewares = params["levels_traefik_max_tcp_middlewares"] + num_routers: int = section["num_routers"] + num_routers_warn: int = section["num_routers_warn"] + num_routers_crit: int = section["num_routers_crit"] + num_services: int = section["num_services"] + num_services_warn: int = section["num_services_warn"] + num_services_crit: int = section["num_services_crit"] + num_middlewares: int = section["num_middlewares"] + num_middlewares_warn: int = section["num_middlewares_warn"] + num_middlewares_crit: int = section["num_middlewares_crit"] + num_components: int = num_routers + num_services + num_middlewares + components_warn: int = num_routers_warn + num_services_warn + num_middlewares_warn + components_crit: int = num_routers_crit + num_services_crit + num_middlewares_crit + components_percent_not_ok: float = 0.0 + if num_components > 0: + components_percent_not_ok = ( + (components_warn + components_crit) * 100 / num_components + ) + yield Metric( + name="traefik_percent_tcp_components_not_ok", + value=components_percent_not_ok, + levels=levels_percent_not_ok, + ) + summary: str = f"Number of TCP routers/services/middlewares: {num_routers}/{num_services}/{num_middlewares}" + details_routers: str = ( + f"Routers WARN: {num_routers_warn}\nRouters CRIT: {num_routers_crit}" + ) + details_services: str = ( + f"Services WARN: {num_services_warn}\nServices CRIT: {num_services_crit}" + ) + details_middlewares: str = f"Middlewares WARN: {num_middlewares_warn}\nMiddlewares CRIT: {num_middlewares_crit}\n\n" + details = f"{details_routers}\n\n{details_services}\n\n{details_middlewares}" + state: State = State.OK + if components_warn > 0: + state = State.WARN + if components_crit > 0: + state = State.CRIT + yield Result( + state=state, + summary=summary, + details=details, + ) + yield from check_levels( + metric_name="traefik_num_tcp_routers", + value=num_routers, + levels_lower=levels_min_routers, + levels_upper=levels_max_routers, + label="Number of TCP routers", + notice_only=True, + render_func=lambda v: "%.0f" % v, + ) + yield from check_levels( + metric_name="traefik_num_tcp_services", + value=num_services, + levels_lower=levels_min_services, + levels_upper=levels_max_services, + label="Number of TCP services", + notice_only=True, + render_func=lambda v: "%.0f" % v, + ) + yield from check_levels( + metric_name="traefik_num_tcp_middlewares", + value=num_middlewares, + levels_lower=levels_min_middlewares, + levels_upper=levels_max_middlewares, + label="Number of TCP middlewares", + notice_only=True, + render_func=lambda v: "%.0f" % v, + ) + + +# create the new agent section, must begin with "agent_section_" +# and must be an instance of "AgentSection" +agent_section_traefik_tcp_components = AgentSection( + # "name" must exactly match the section name within the agent output + name="traefik_tcp_components", + # define the parse function, name is arbitrary, a good choice is to choose + # "parse_" as prefix and append the section name + parse_function=parse_traefik_tcp_components, +) + +# create the new check plugin, must begin with "check_plugin_" +# and must be an instance of "CheckPlugin" +check_plugin_traefik_tcp_components = CheckPlugin( + # "name" should be the same as the corresponding section within the agent output + name="traefik_tcp_components", + service_name="Traefik TCP components", + # define the discovery function, name is arbitrary, a good choice is to choose + # "discover_" as prefix and append the section name + discovery_function=discover_traefik_tcp_components, + # define the check function, name is arbitrary, a good choice is to choose + # "check_" as prefix and append the section name + check_function=check_traefik_tcp_components, + # define the default parameters + check_default_parameters={ + "levels_traefik_min_tcp_routers": ("fixed", (0, 0)), + "levels_traefik_max_tcp_routers": ("fixed", (25, 50)), + "levels_traefik_min_tcp_services": ("fixed", (0, 0)), + "levels_traefik_max_tcp_services": ("fixed", (25, 50)), + "levels_traefik_min_tcp_middlewares": ("fixed", (0, 0)), + "levels_traefik_max_tcp_middlewares": ("fixed", (25, 50)), + "levels_traefik_tcp_components_not_ok": ("fixed", (0.5, 1.0)), + }, + # connect to the ruleset where parameters can be defined + # must match the name of the ruleset exactly + check_ruleset_name="traefik_tcp_components", +) diff --git a/traefik/agent_based/traefik_udp_components.py b/traefik/agent_based/traefik_udp_components.py new file mode 100644 index 0000000..4002e62 --- /dev/null +++ b/traefik/agent_based/traefik_udp_components.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# pylint: disable=missing-module-docstring, unused-argument, consider-using-f-string +# pylint: disable=missing-function-docstring, line-too-long + + +# import necessary elements from API version 2 +from cmk.agent_based.v2 import ( + AgentSection, + CheckPlugin, + Service, + State, + Metric, + Result, + DiscoveryResult, + CheckResult, + check_levels, +) + + +def parse_traefik_udp_components(string_table): + """the parse function""" + parsed_data = {} + for line in string_table: + if line[0] == "routers": + parsed_data["num_routers"] = int(line[1]) + parsed_data["num_routers_warn"] = int(line[2]) + parsed_data["num_routers_crit"] = int(line[3]) + elif line[0] == "services": + parsed_data["num_services"] = int(line[1]) + parsed_data["num_services_warn"] = int(line[2]) + parsed_data["num_services_crit"] = int(line[3]) + return parsed_data + + +def discover_traefik_udp_components(section) -> DiscoveryResult: + """the discover function""" + yield Service() + + +def check_traefik_udp_components(params, section) -> CheckResult: + """the check function""" + _level_type, levels_percent_not_ok = params["levels_traefik_udp_components_not_ok"] + levels_min_routers = params["levels_traefik_min_udp_routers"] + levels_max_routers = params["levels_traefik_max_udp_routers"] + levels_min_services = params["levels_traefik_min_udp_services"] + levels_max_services = params["levels_traefik_max_udp_services"] + num_routers: int = section["num_routers"] + num_routers_warn: int = section["num_routers_warn"] + num_routers_crit: int = section["num_routers_crit"] + num_services: int = section["num_services"] + num_services_warn: int = section["num_services_warn"] + num_services_crit: int = section["num_services_crit"] + num_components: int = num_routers + num_services + components_warn: int = num_routers_warn + num_services_warn + components_crit: int = num_routers_crit + num_services_crit + components_percent_not_ok: float = 0.0 + if num_components > 0: + components_percent_not_ok = ( + (components_warn + components_crit) * 100 / num_components + ) + yield Metric( + name="traefik_percent_udp_components_not_ok", + value=components_percent_not_ok, + levels=levels_percent_not_ok, + ) + summary: str = f"Number of UDP routers/services: {num_routers}/{num_services}" + details_routers: str = ( + f"Routers WARN: {num_routers_warn}\nRouters CRIT: {num_routers_crit}" + ) + details_services: str = ( + f"Services WARN: {num_services_warn}\nServices CRIT: {num_services_crit}" + ) + details = f"{details_routers}\n\n{details_services}" + state: State = State.OK + if components_warn > 0: + state = State.WARN + if components_crit > 0: + state = State.CRIT + yield Result( + state=state, + summary=summary, + details=details, + ) + yield from check_levels( + metric_name="traefik_num_udp_routers", + value=num_routers, + levels_lower=levels_min_routers, + levels_upper=levels_max_routers, + label="Number of UDP routers", + notice_only=True, + render_func=lambda v: "%.0f" % v, + ) + yield from check_levels( + metric_name="traefik_num_udp_services", + value=num_services, + levels_lower=levels_min_services, + levels_upper=levels_max_services, + label="Number of UDP services", + notice_only=True, + render_func=lambda v: "%.0f" % v, + ) + + +# create the new agent section, must begin with "agent_section_" +# and must be an instance of "AgentSection" +agent_section_traefik_udp_components = AgentSection( + # "name" must exactly match the section name within the agent output + name="traefik_udp_components", + # define the parse function, name is arbitrary, a good choice is to choose + # "parse_" as prefix and append the section name + parse_function=parse_traefik_udp_components, +) + +# create the new check plugin, must begin with "check_plugin_" +# and must be an instance of "CheckPlugin" +check_plugin_traefik_udp_components = CheckPlugin( + # "name" should be the same as the corresponding section within the agent output + name="traefik_udp_components", + service_name="Traefik UDP components", + # define the discovery function, name is arbitrary, a good choice is to choose + # "discover_" as prefix and append the section name + discovery_function=discover_traefik_udp_components, + # define the check function, name is arbitrary, a good choice is to choose + # "check_" as prefix and append the section name + check_function=check_traefik_udp_components, + # define the default parameters + check_default_parameters={ + "levels_traefik_min_udp_routers": ("fixed", (0, 0)), + "levels_traefik_max_udp_routers": ("fixed", (25, 50)), + "levels_traefik_min_udp_services": ("fixed", (0, 0)), + "levels_traefik_max_udp_services": ("fixed", (25, 50)), + "levels_traefik_udp_components_not_ok": ("fixed", (0.5, 1.0)), + }, + # connect to the ruleset where parameters can be defined + # must match the name of the ruleset exactly + check_ruleset_name="traefik_udp_components", +) diff --git a/traefik/checkman/traefik_http_components b/traefik/checkman/traefik_http_components new file mode 100644 index 0000000..3a07e2b --- /dev/null +++ b/traefik/checkman/traefik_http_components @@ -0,0 +1,11 @@ +title: Traefik HTTP components +agents: linux +catalog: unsorted +license: GPL +distribution: check_mk +description: + Shows total number of HTTP routers/services/middlewares. + The check will raise WARN/CRIT if the min/max numbers of routers/services/middlewares are below/above the configurable levels. + The check will raise WARN/CRIT if the overall number of components in not OK state (reported directly from the Traefik API) is above the configurable levels. +inventory: + one service is created (with several details) \ No newline at end of file diff --git a/traefik/checkman/traefik_info b/traefik/checkman/traefik_info new file mode 100644 index 0000000..bb75f55 --- /dev/null +++ b/traefik/checkman/traefik_info @@ -0,0 +1,10 @@ +title: Traefik Various information +agents: linux +catalog: unsorted +license: GPL +distribution: check_mk +description: + Shows several information about a Traefik instance, e.g. version and code name. + The check will raise WARN/CRIT if the agent execution time is above the configurable levels. +inventory: + one service is created (with several details) \ No newline at end of file diff --git a/traefik/checkman/traefik_tcp_components b/traefik/checkman/traefik_tcp_components new file mode 100644 index 0000000..8e70de8 --- /dev/null +++ b/traefik/checkman/traefik_tcp_components @@ -0,0 +1,11 @@ +title: Traefik TCP components +agents: linux +catalog: unsorted +license: GPL +distribution: check_mk +description: + Shows total number of TCP routers/services/middlewares. + The check will raise WARN/CRIT if the min/max numbers of routers/services/middlewares are below/above the configurable levels. + The check will raise WARN/CRIT if the overall number of components in not OK state (reported directly from the Traefik API) is above the configurable levels. +inventory: + one service is created (with several details) \ No newline at end of file diff --git a/traefik/checkman/traefik_udp_components b/traefik/checkman/traefik_udp_components new file mode 100644 index 0000000..3627a05 --- /dev/null +++ b/traefik/checkman/traefik_udp_components @@ -0,0 +1,11 @@ +title: Traefik UDP components +agents: linux +catalog: unsorted +license: GPL +distribution: check_mk +description: + Shows total number of UDP routers/services. + The check will raise WARN/CRIT if the min/max numbers of routers/services are below/above the configurable levels. + The check will raise WARN/CRIT if the overall number of components in not OK state (reported directly from the Traefik API) is above the configurable levels. +inventory: + one service is created (with several details) \ No newline at end of file diff --git a/traefik/graphing/graph_traefik.py b/traefik/graphing/graph_traefik.py new file mode 100644 index 0000000..1d733c4 --- /dev/null +++ b/traefik/graphing/graph_traefik.py @@ -0,0 +1,108 @@ +# 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 + + +# info section +metric_traefik_agent_execution_time = Metric( + # "name" must be exactly the "metric_name" within the check function + name="traefik_agent_execution_time", + title=Title("Agent execution time"), + unit=Unit(DecimalNotation("ms")), + color=Color.DARK_ORANGE, +) + +# HTTP section +metric_traefik_num_http_routers = Metric( + name="traefik_num_http_routers", + title=Title("Number of HTTP routers"), + unit=Unit(DecimalNotation("")), + color=Color.LIGHT_GREEN, +) + +metric_traefik_num_http_services = Metric( + name="traefik_num_http_services", + title=Title("Number of HTTP services"), + unit=Unit(DecimalNotation("")), + color=Color.LIGHT_BLUE, +) + +metric_traefik_num_http_middlewares = Metric( + name="traefik_num_http_middlewares", + title=Title("Number of HTTP middlewares"), + unit=Unit(DecimalNotation("")), + color=Color.LIGHT_RED, +) + + +metric_traefik_percent_http_components_not_ok = Metric( + name="traefik_percent_http_components_not_ok", + title=Title("Percent of HTTP components in not OK state"), + unit=Unit(DecimalNotation("%")), + color=Color.DARK_RED, +) + +# TCP section +metric_traefik_num_tcp_routers = Metric( + name="traefik_num_tcp_routers", + title=Title("Number of TCP routers"), + unit=Unit(DecimalNotation("")), + color=Color.LIGHT_GREEN, +) + +metric_traefik_num_tcp_services = Metric( + name="traefik_num_tcp_services", + title=Title("Number of TCP services"), + unit=Unit(DecimalNotation("")), + color=Color.LIGHT_BLUE, +) + +metric_traefik_num_tcp_middlewares = Metric( + name="traefik_num_tcp_middlewares", + title=Title("Number of TCP middlewares"), + unit=Unit(DecimalNotation("")), + color=Color.LIGHT_RED, +) + +metric_traefik_percent_tcp_components_not_ok = Metric( + name="traefik_percent_tcp_components_not_ok", + title=Title("Percent of TCP components in not OK state"), + unit=Unit(DecimalNotation("%")), + color=Color.DARK_RED, +) + +# UDP section +metric_traefik_num_udp_routers = Metric( + name="traefik_num_udp_routers", + title=Title("Number of UDP routers"), + unit=Unit(DecimalNotation("")), + color=Color.LIGHT_GREEN, +) + +metric_traefik_num_udp_services = Metric( + name="traefik_num_udp_services", + title=Title("Number of UDP services"), + unit=Unit(DecimalNotation("")), + color=Color.LIGHT_BLUE, +) + +metric_traefik_percent_udp_components_not_ok = Metric( + name="traefik_percent_udp_components_not_ok", + title=Title("Percent of UDP components in not OK state"), + unit=Unit(DecimalNotation("%")), + color=Color.DARK_RED, +) + + +# Perfometers +perfometer_traefik_agent_execution_time = Perfometer( + name="traefik_agent_execution_time", + focus_range=FocusRange(Closed(0), Closed(5000)), + # "segments" must be exactly the name of the metric + segments=["traefik_agent_execution_time"], +) diff --git a/traefik/libexec/agent_traefik b/traefik/libexec/agent_traefik new file mode 100755 index 0000000..109f40f --- /dev/null +++ b/traefik/libexec/agent_traefik @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 +# pylint: disable=too-many-instance-attributes, pointless-string-statement +"""CheckMK Special Agent for Traefik""" + +import time +import argparse +from typing import Dict, Union, Optional, Final + +import requests +import urllib3 +from requests.auth import HTTPDigestAuth, HTTPBasicAuth + +# dictionary with all parameters +ArgumentsMap = Dict[str, Union[str, bool, None]] + +TIMEOUT: Final[int] = 30 +NOT_AVAIL: Final[str] = "N/A" +AGENT_VERSION: Final[str] = "0.1.0" + + +class TraefikAPI: + """Traefik API Class""" + + def __init__(self, args: ArgumentsMap): + """Initialize TraefikAPI""" + self.start_time: float = time.perf_counter() + # set arguments + self.hostname: str = args.get("hostname") + self.username: str = args.get("username") + self.password: str = args.get("password") + self.auth_type: str = args.get("auth_type") + self.no_http_check: bool = args.get("no_http_check", False) + self.no_tcp_check: bool = args.get("no_tcp_check", False) + self.no_udp_check: bool = args.get("no_udp_check", False) + self.no_cert_check: bool = args.get("no_cert_check", False) + self.no_https: bool = args.get("no_https", False) + # define other attributes + if self.no_cert_check: + # disable certificate warnings + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + self.verify = False + else: + self.verify = True + if self.no_https: + self.protocol = "http" + self.port = "80" + else: + self.protocol = "https" + self.port = "443" + if args.get("port"): + self.port = args.get("port") + + self.base_url: str = f"{self.protocol}://{self.hostname}:{self.port}/api" + # print(f"Base URL: {self.base_url}") + self.session: Optional[requests.Session] = None + self.overview: Optional[dict] = None + + def __enter__(self): + self.session: requests.Session = requests.Session() + if self.auth_type == "basic": + self.session.auth = HTTPBasicAuth(self.username, self.password) + elif self.auth_type == "digest": + self.session.auth = HTTPDigestAuth(self.username, self.password) + else: + print( + f"Error: Invalid authentication type '{self.auth_type}'. Use 'basic' or 'digest'." + ) + return None + return self + + def __exit__(self, _exc_type, _exc_value, _traceback) -> None: + if self.session: + self.session.close() + end_time: float = time.perf_counter() + duration: float = end_time - self.start_time + traefik_info: Optional[dict] = self._get_version() + if traefik_info: + print("<<>>") + print(f"Traefik_Version;{traefik_info.get('Version', NOT_AVAIL)}") + print(f"Traefik_CodeName;{traefik_info.get('Codename', NOT_AVAIL)}") + print(f"Traefik_StartDate;{traefik_info.get('startDate', NOT_AVAIL)}") + print(f"Agent_Runtime;{duration}") + print(f"Agent_Version;{AGENT_VERSION}") + + def _get_version(self) -> Optional[dict]: + """Get data from API endpoint version""" + try: + response: requests.Response = self.session.get( + f"{self.base_url}/version", timeout=TIMEOUT, verify=self.verify + ) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + print(f"Error fetching version data: {e}") + return None + + def get_overview(self) -> Optional[dict]: + """Get data from API endpoint overview""" + try: + response: requests.Response = self.session.get( + f"{self.base_url}/overview", timeout=TIMEOUT, verify=self.verify + ) + response.raise_for_status() + self.overview = response.json() + return self.overview + except requests.RequestException as e: + print(f"Error fetching overview data: {e}") + return None + + def create_section_output(self) -> None: + """Create section output""" + total: int = 0 + warnings: int = 0 + errors: int = 0 + data: dict[str, dict[str, int]] = {} + + if not self.no_http_check: + print("<<>>") + # Process HTTP components + data = self.overview.get("http", {}) + for component in ["routers", "services", "middlewares"]: + total = data.get(component, {}).get("total", -1) + warnings = data.get(component, {}).get("warnings", -1) + errors = data.get(component, {}).get("errors", -1) + print(f"{component};{total};{warnings};{errors}") + if not self.no_tcp_check: + print("<<>>") + # Process TCP components + data = self.overview.get("tcp", {}) + for component in ["routers", "services", "middlewares"]: + total = data.get(component, {}).get("total", -1) + warnings = data.get(component, {}).get("warnings", -1) + errors = data.get(component, {}).get("errors", -1) + print(f"{component};{total};{warnings};{errors}") + if not self.no_udp_check: + print("<<>>") + # Process UDP components + data = self.overview.get("udp", {}) + for component in ["routers", "services"]: + total = data.get(component, {}).get("total", -1) + warnings = data.get(component, {}).get("warnings", -1) + errors = data.get(component, {}).get("errors", -1) + print(f"{component};{total};{warnings};{errors}") + + +def parse_arguments() -> ArgumentsMap: + """Parse command-line arguments""" + parser = argparse.ArgumentParser( + description="Parameters to connect to the API of a Traefik instance", + ) + # Required arguments + parser.add_argument( + "--hostname", + type=str, + required=True, + help="The hostname (FQDN) or IP address of the Traefik instance.", + ) + parser.add_argument( + "--username", + type=str, + required=True, + help="The username for authentication.", + ) + parser.add_argument( + "--password", + type=str, + required=True, + help="The password for authentication.", + ) + parser.add_argument( + "--auth-type", + type=str, + required=True, + choices=["basic", "digest"], # Restrict allowed values + help="The authentication type to use ('basic' or 'digest').", + ) + parser.add_argument( + "--no-http-check", + action="store_true", + default=False, + help="Disable HTTP components check (optional, default=no).", + ) + parser.add_argument( + "--no-tcp-check", + action="store_true", + default=False, + help="Disable TCP components check (optional, default=no).", + ) + parser.add_argument( + "--no-udp-check", + action="store_true", + default=False, + help="Disable UDP components check (optional, default=no).", + ) + parser.add_argument( + "--no-cert-check", + action="store_true", + default=False, + help="Disable certificate check (optional, default=no).", + ) + parser.add_argument( + "--no-https", + action="store_true", + default=False, + help="Disable HTTPS (optional, default=no).", + ) + parser.add_argument( + "--port", + type=int, + required=False, + help="Port if not listening to HTTP(S) on default ports 80/443 (optional).", + ) + args: argparse.Namespace = parser.parse_args() + arg_map: ArgumentsMap = vars(args) + return arg_map + + +def main() -> None: + """Main function""" + args: ArgumentsMap = parse_arguments() + with TraefikAPI(args=args) as traefik: + overview = traefik.get_overview() + if overview: + traefik.create_section_output() + + +if __name__ == "__main__": + main() + +""" + +Sample output +component;total number of components;number of warnings;number of errors + +<<>> +routers;50;0;0 +services;52;0;0 +middlewares;5;0;0 +<<>> +routers;1;0;0 +services;1;0;0 +middlewares;0;0;0 +<<>> +routers;0;0;0 +services;0;0;0 + +## General Traefik info +Lines contain + Traefik Version + Traefik Codename + Traefik Start Date + Runtime of script (in seconds) + Agent Version + +<<>> +Traefik_Version;3.3.5 +Traefik_CodeName;saintnectaire +Traefik_StartDate;2025-04-18T19:01:01.706796728+02:00 +Agent_Runtime;0.3067862739553675 +Agent_Version;0.1.0 + +""" diff --git a/traefik/rulesets/rs_traefik_http.py b/traefik/rulesets/rs_traefik_http.py new file mode 100644 index 0000000..10cab62 --- /dev/null +++ b/traefik/rulesets/rs_traefik_http.py @@ -0,0 +1,122 @@ +#!/user/bin/env python3 +"""HTTP components parameter form for Traefik""" + +from cmk.rulesets.v1 import Title, Help +from cmk.rulesets.v1.form_specs import ( + DictElement, + Dictionary, + SimpleLevels, + DefaultValue, + Integer, + LevelDirection, + Float, +) + +from cmk.rulesets.v1.rule_specs import CheckParameters, Topic, HostCondition + + +# function name should begin with an underscore to limit it's visibility +def _parameter_form(): + return Dictionary( + elements={ + "levels_traefik_min_http_routers": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for minimum number of HTTP routers"), + help_text=Help( + "Define the levels for the minimum number of HTTP routers" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.LOWER, + prefill_fixed_levels=DefaultValue(value=(10, 5)), + ), + required=True, + ), + "levels_traefik_max_http_routers": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for maximum number of HTTP routers"), + help_text=Help( + "Define the levels for the maximum number of HTTP routers" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(75, 100)), + ), + required=True, + ), + "levels_traefik_min_http_services": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for minimum number of HTTP services"), + help_text=Help( + "Define the levels for the minimum number of HTTP services" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.LOWER, + prefill_fixed_levels=DefaultValue(value=(10, 5)), + ), + required=True, + ), + "levels_traefik_max_http_services": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for maximum number of HTTP services"), + help_text=Help( + "Define the levels for the maximum number of HTTP services" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(75, 100)), + ), + required=True, + ), + "levels_traefik_min_http_middlewares": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for minimum number of HTTP middlewares"), + help_text=Help( + "Define the levels for the minimum number of HTTP middlewares" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.LOWER, + prefill_fixed_levels=DefaultValue(value=(5, 2)), + ), + required=True, + ), + "levels_traefik_max_http_middlewares": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for maximum number of HTTP middlewares"), + help_text=Help( + "Define the levels for the maximum number of HTTP middlewares" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(20, 50)), + ), + required=True, + ), + "levels_traefik_http_components_not_ok": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for percentage of not OK HTTP components"), + help_text=Help( + "Define the levels for the maximum number of HTTP components in not OK state" + ), + form_spec_template=Float(unit_symbol="%"), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(0.5, 1.0)), + ), + required=True, + ), + } + ) + + +# name must begin with "rule_spec_", should refer to the used check plugin +# must be an instance of "CheckParameters" +rule_spec_traefik_http_components = CheckParameters( + # "name" should be the same as the check plugin + name="traefik_http_components", + # the title is shown in the GUI + title=Title("Traefik HTTP components parameters"), + # this ruleset can be found under Setup|Service monitoring rules|Applications... + topic=Topic.APPLICATIONS, + # define the name of the function which creates the GUI elements + parameter_form=_parameter_form, + condition=HostCondition(), +) diff --git a/traefik/rulesets/rs_traefik_info.py b/traefik/rulesets/rs_traefik_info.py new file mode 100644 index 0000000..317ddce --- /dev/null +++ b/traefik/rulesets/rs_traefik_info.py @@ -0,0 +1,49 @@ +#!/user/bin/env python3 +"""general parameter form for Traefik""" + +from cmk.rulesets.v1 import Title, Help +from cmk.rulesets.v1.form_specs import ( + DictElement, + Dictionary, + SimpleLevels, + DefaultValue, + Float, + LevelDirection, +) + +from cmk.rulesets.v1.rule_specs import CheckParameters, Topic, HostCondition + + +# function name should begin with an underscore to limit it's visibility +def _parameter_form(): + return Dictionary( + elements={ + "levels_traefik_agent_execution_time": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for agent execution time"), + help_text=Help( + "Define the levels for the maximum agent execution time" + ), + form_spec_template=Float(unit_symbol="ms"), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(500.0, 750.0)), + ), + required=True, + ), + } + ) + + +# name must begin with "rule_spec_", should refer to the used check plugin +# must be an instance of "CheckParameters" +rule_spec_traefik_info = CheckParameters( + # "name" should be the same as the check plugin + name="traefik_info", + # the title is shown in the GUI + title=Title("Traefik general parameters"), + # this ruleset can be found under Setup|Service monitoring rules|Applications... + topic=Topic.APPLICATIONS, + # define the name of the function which creates the GUI elements + parameter_form=_parameter_form, + condition=HostCondition(), +) diff --git a/traefik/rulesets/rs_traefik_params.py b/traefik/rulesets/rs_traefik_params.py new file mode 100644 index 0000000..703525b --- /dev/null +++ b/traefik/rulesets/rs_traefik_params.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# pylint: disable=line-too-long +"""defines the form for typing in all needed Traefik 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, + SingleChoice, + SingleChoiceElement, +) +from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic + + +def _form_spec_special_agent_traefik() -> Dictionary: + return Dictionary( + title=Title("Traefik Server Information"), + help_text=Help("Checking Traefik systems via API"), + elements={ + "hostname": DictElement( + required=True, + parameter_form=String( + title=Title("Hostname"), + help_text=Help( + "Hostname of Traefik server (bare FQDN or IP), mandatory, eg. traefik.yourdomain.tld" + ), + custom_validate=(validators.LengthInRange(min_value=1),), + prefill=InputHint("traefik.yourdomain.tld"), + ), + ), + "username": DictElement( + required=True, + parameter_form=String( + title=Title("Username"), + help_text=Help("Username for authentification, mandatory"), + custom_validate=(validators.LengthInRange(min_value=1),), + prefill=InputHint("traefikadmin"), + ), + ), + "password": DictElement( + required=True, + parameter_form=Password( + title=Title("Password"), + help_text=Help("Specify password, mandatory"), + custom_validate=(validators.LengthInRange(min_value=1),), + migrate=migrate_to_password, + ), + ), + "auth_type": DictElement( + required=True, + parameter_form=SingleChoice( + title=Title("Authentification type"), + help_text=Help("Type of authentification to use"), + elements=[ + SingleChoiceElement( + name="basic", + title=Title("Basic"), + ), + SingleChoiceElement( + name="digest", + title=Title("Digest"), + ), + ], + ), + ), + "no_http_check": DictElement( + required=False, + parameter_form=BooleanChoice( + title=Title("Disable HTTP components"), + help_text=Help( + "Activate to disable check of HTTP components, optional" + ), + label=Label("Disable HTTP components"), + ), + ), + "no_tcp_check": DictElement( + required=False, + parameter_form=BooleanChoice( + title=Title("Disable TCP components"), + help_text=Help( + "Activate to disable check of TCP components, optional" + ), + label=Label("Disable TCP components"), + ), + ), + "no_udp_check": DictElement( + required=False, + parameter_form=BooleanChoice( + title=Title("Disable UDP components"), + help_text=Help( + "Activate to disable check of UDP components, optional" + ), + label=Label("Disable UDP components"), + ), + ), + "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"), + ), + ), + "port": DictElement( + required=False, + parameter_form=Integer( + title=Title("Port"), + help_text=Help( + "Specify port the Traefik system ist listening on (if not 80/443), optional" + ), + prefill=DefaultValue(443), + custom_validate=(validators.NetworkPort(),), + ), + ), + }, + ) + + +rule_spec_traefik = SpecialAgent( + name="traefik", + title=Title("Traefik connection parameters"), + topic=Topic.APPLICATIONS, + parameter_form=_form_spec_special_agent_traefik, +) diff --git a/traefik/rulesets/rs_traefik_tcp.py b/traefik/rulesets/rs_traefik_tcp.py new file mode 100644 index 0000000..bb14862 --- /dev/null +++ b/traefik/rulesets/rs_traefik_tcp.py @@ -0,0 +1,122 @@ +#!/user/bin/env python3 +"""HTTP components parameter form for Traefik""" + +from cmk.rulesets.v1 import Title, Help +from cmk.rulesets.v1.form_specs import ( + DictElement, + Dictionary, + SimpleLevels, + DefaultValue, + Integer, + LevelDirection, + Float, +) + +from cmk.rulesets.v1.rule_specs import CheckParameters, Topic, HostCondition + + +# function name should begin with an underscore to limit it's visibility +def _parameter_form(): + return Dictionary( + elements={ + "levels_traefik_min_tcp_routers": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for minimum number of TCP routers"), + help_text=Help( + "Define the levels for the minimum number of TCP routers" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.LOWER, + prefill_fixed_levels=DefaultValue(value=(0, 0)), + ), + required=True, + ), + "levels_traefik_max_tcp_routers": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for maximum number of TCP routers"), + help_text=Help( + "Define the levels for the maximum number of TCP routers" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(25, 50)), + ), + required=True, + ), + "levels_traefik_min_tcp_services": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for minimum number of TCP services"), + help_text=Help( + "Define the levels for the minimum number of TCP services" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.LOWER, + prefill_fixed_levels=DefaultValue(value=(0, 0)), + ), + required=True, + ), + "levels_traefik_max_tcp_services": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for maximum number of TCP services"), + help_text=Help( + "Define the levels for the maximum number of TCP services" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(25, 50)), + ), + required=True, + ), + "levels_traefik_min_tcp_middlewares": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for minimum number of TCP middlewares"), + help_text=Help( + "Define the levels for the minimum number of TCP middlewares" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.LOWER, + prefill_fixed_levels=DefaultValue(value=(0, 0)), + ), + required=True, + ), + "levels_traefik_max_tcp_middlewares": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for maximum number of TCP middlewares"), + help_text=Help( + "Define the levels for the maximum number of TCP middlewares" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(25, 50)), + ), + required=True, + ), + "levels_traefik_tcp_components_not_ok": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for percentage of not OK TCP components"), + help_text=Help( + "Define the levels for the maximum number of TCP components in not OK state" + ), + form_spec_template=Float(unit_symbol="%"), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(0.5, 1.0)), + ), + required=True, + ), + } + ) + + +# name must begin with "rule_spec_", should refer to the used check plugin +# must be an instance of "CheckParameters" +rule_spec_traefik_tcp_components = CheckParameters( + # "name" should be the same as the check plugin + name="traefik_tcp_components", + # the title is shown in the GUI + title=Title("Traefik TCP components parameters"), + # this ruleset can be found under Setup|Service monitoring rules|Applications... + topic=Topic.APPLICATIONS, + # define the name of the function which creates the GUI elements + parameter_form=_parameter_form, + condition=HostCondition(), +) diff --git a/traefik/rulesets/rs_traefik_udp.py b/traefik/rulesets/rs_traefik_udp.py new file mode 100644 index 0000000..3cfe8e9 --- /dev/null +++ b/traefik/rulesets/rs_traefik_udp.py @@ -0,0 +1,98 @@ +#!/user/bin/env python3 +"""UDP components parameter form for Traefik""" + +from cmk.rulesets.v1 import Title, Help +from cmk.rulesets.v1.form_specs import ( + DictElement, + Dictionary, + SimpleLevels, + DefaultValue, + Integer, + LevelDirection, + Float, +) + +from cmk.rulesets.v1.rule_specs import CheckParameters, Topic, HostCondition + + +# function name should begin with an underscore to limit it's visibility +def _parameter_form(): + return Dictionary( + elements={ + "levels_traefik_min_udp_routers": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for minimum number of UDP routers"), + help_text=Help( + "Define the levels for the minimum number of UDP routers" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.LOWER, + prefill_fixed_levels=DefaultValue(value=(0, 0)), + ), + required=True, + ), + "levels_traefik_max_udp_routers": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for maximum number of UDP routers"), + help_text=Help( + "Define the levels for the maximum number of UDP routers" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(25, 50)), + ), + required=True, + ), + "levels_traefik_min_udp_services": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for minimum number of UDP services"), + help_text=Help( + "Define the levels for the minimum number of UDP services" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.LOWER, + prefill_fixed_levels=DefaultValue(value=(0, 0)), + ), + required=True, + ), + "levels_traefik_max_udp_services": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for maximum number of UDP services"), + help_text=Help( + "Define the levels for the maximum number of UDP services" + ), + form_spec_template=Integer(unit_symbol=""), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(25, 50)), + ), + required=True, + ), + "levels_traefik_udp_components_not_ok": DictElement( + parameter_form=SimpleLevels( + title=Title("Levels for percentage of not OK UDP components"), + help_text=Help( + "Define the levels for the maximum number of UDP components in not OK state" + ), + form_spec_template=Float(unit_symbol="%"), + level_direction=LevelDirection.UPPER, + prefill_fixed_levels=DefaultValue(value=(0.5, 1.0)), + ), + required=True, + ), + } + ) + + +# name must begin with "rule_spec_", should refer to the used check plugin +# must be an instance of "CheckParameters" +rule_spec_traefik_udp_components = CheckParameters( + # "name" should be the same as the check plugin + name="traefik_udp_components", + # the title is shown in the GUI + title=Title("Traefik UDP components parameters"), + # this ruleset can be found under Setup|Service monitoring rules|Applications... + topic=Topic.APPLICATIONS, + # define the name of the function which creates the GUI elements + parameter_form=_parameter_form, + condition=HostCondition(), +) diff --git a/traefik/server_side_calls/agent_traefik.py b/traefik/server_side_calls/agent_traefik.py new file mode 100644 index 0000000..53ab535 --- /dev/null +++ b/traefik/server_side_calls/agent_traefik.py @@ -0,0 +1,59 @@ +"""Traefik parameter handling for the special agent""" + +from collections.abc import Iterable + +from pydantic import BaseModel + +from cmk.server_side_calls.v1 import ( + HostConfig, + Secret, + SpecialAgentCommand, + SpecialAgentConfig, +) + + +class TraefikArgs(BaseModel): + """defines all needed parameters for the special agent""" + + hostname: str + username: str + password: Secret + auth_type: str + no_http_check: bool = False + no_tcp_check: bool = False + no_udp_check: bool = False + no_cert_check: bool = False + no_https: bool = False + port: int | None = None + + +def agent_traefik_arguments( + params: TraefikArgs, _host_config: HostConfig +) -> Iterable[SpecialAgentCommand]: + """replaces the argument_thingy from the old API""" + command_arguments: list[str | Secret] = [] + command_arguments += ["--hostname", params.hostname] + command_arguments += ["--username", params.username] + command_arguments += ["--password", params.password.unsafe()] + command_arguments += ["--auth-typ", params.auth_type] + if params.no_http_check: + command_arguments.append("--no-http-check") + if params.no_tcp_check: + command_arguments.append("--no-tcp-check") + if params.no_udp_check: + command_arguments.append("--no-udp-check") + if params.no_https: + command_arguments.append("--no-https") + if params.no_cert_check: + command_arguments.append("--no-cert-check") + if params.port is not None: + command_arguments += ["--port", str(params.port)] + yield SpecialAgentCommand(command_arguments=command_arguments) + + +special_agent_traefik = SpecialAgentConfig( + # name must be the filename of the executable for the special agent (without prefix) + name="traefik", + parameter_parser=TraefikArgs.model_validate, + commands_function=agent_traefik_arguments, +)