From 6b63ff4fc0b767afd81373ec05fe995c5cca8090 Mon Sep 17 00:00:00 2001 From: "C. Pommer" <39278813+CLiX-1@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:29:13 +0200 Subject: [PATCH] Fix count enabled/warning --- m365/agent_based/m365_licenses.py | 16 ++--- m365/checkman/m365_licenses | 23 ++++++- m365/graphing/m365_licenses.py | 19 ++++-- m365/libexec/agent_m365 | 101 +++++++++++++++--------------- 4 files changed, 93 insertions(+), 66 deletions(-) diff --git a/m365/agent_based/m365_licenses.py b/m365/agent_based/m365_licenses.py index 0f7af83..a6a8d17 100644 --- a/m365/agent_based/m365_licenses.py +++ b/m365/agent_based/m365_licenses.py @@ -103,8 +103,10 @@ def check_m365_licenses(item: str, params: Mapping[str, Any], section: Section) lic_units_suspended = license["lic_units_suspended"] lic_units_warning = license["lic_units_warning"] - lic_units_consumed_pct = round(lic_units_consumed / lic_units_enabled * 100, 2) - lic_units_available = lic_units_enabled - lic_units_consumed + lic_units_total = lic_units_enabled + lic_units_warning + + lic_units_consumed_pct = round(lic_units_consumed / lic_units_total * 100, 2) + lic_units_available = lic_units_total - lic_units_consumed result_level = "" result_state = State.OK @@ -116,7 +118,7 @@ def check_m365_licenses(item: str, params: Mapping[str, Any], section: Section) if params_levels_available[0] == "lic_unit_available_lower_pct": levels_consumed_pct = (100 - warning_level, 100 - critical_level) - available_percent = lic_units_available / lic_units_enabled * 100 + available_percent = lic_units_available / lic_units_total * 100 if available_percent < critical_level: result_state = State.CRIT @@ -128,7 +130,7 @@ def check_m365_licenses(item: str, params: Mapping[str, Any], section: Section) ) else: - levels_consumed_abs = (lic_units_enabled - warning_level, lic_units_enabled - critical_level) + levels_consumed_abs = (lic_units_total - warning_level, lic_units_total - critical_level) if lic_units_consumed > levels_consumed_abs[1]: result_state = State.CRIT @@ -138,7 +140,7 @@ def check_m365_licenses(item: str, params: Mapping[str, Any], section: Section) result_level = f" (warn/crit below {warning_level}/{critical_level} available)" result_summary = ( - f"Consumed: {render.percent(lic_units_consumed_pct)} - {lic_units_consumed} of {lic_units_enabled}" + f"Consumed: {render.percent(lic_units_consumed_pct)} - {lic_units_consumed} of {lic_units_total}" f", Available: {lic_units_available}" f"{result_level}" f"{', Warning: ' + str(lic_units_warning) if lic_units_warning > 0 else ''}" @@ -160,12 +162,12 @@ def check_m365_licenses(item: str, params: Mapping[str, Any], section: Section) details=result_details, ) + yield Metric(name="m365_licenses_total", value=lic_units_total) yield Metric(name="m365_licenses_enabled", value=lic_units_enabled) yield Metric(name="m365_licenses_consumed", value=lic_units_consumed, levels=levels_consumed_abs) yield Metric(name="m365_licenses_consumed_pct", value=lic_units_consumed_pct, levels=levels_consumed_pct) yield Metric(name="m365_licenses_available", value=lic_units_available) - if lic_units_warning > 0: - yield Metric(name="m365_licenses_warning", value=lic_units_warning + lic_units_enabled) + yield Metric(name="m365_licenses_warning", value=lic_units_warning) agent_section_m365_licenses = AgentSection( diff --git a/m365/checkman/m365_licenses b/m365/checkman/m365_licenses index f284834..8a983ff 100644 --- a/m365/checkman/m365_licenses +++ b/m365/checkman/m365_licenses @@ -4,15 +4,34 @@ catalog: cloud/Microsoft license: GPLv2 distribution: Christopher Pommer description: - This check monitors the active licenses from a Microsoft 365 tenant. + This check monitors the Microsoft licenses (enabled and warning) + from a Microsoft 365 tenant. Depending on the configured check levels, the service is in state {OK}, {WARN} or {CRIT}. You have to configure the special agent {Microsoft 365}. + {enabled:} + The number of units that are enabled for the active subscription of + the service SKU. + + {lockedOut:} + The number of units that are locked out because the customer canceled + their subscription of the service SKU. + + {suspended:} + The number of units that are suspended because the subscription of + the service SKU has been canceled. The units can't be assigned but + can still be reactivated before they're deleted. + + {warning:} + The number of units that are in warning status. When the subscription + of the service SKU has expired, the customer has a grace period to + renew their subscription before it's canceled (moved to a suspended state). + item: M365 license SKU name. discovery: - One service is created for each active Microsoft 365 license. + One service is created for each Microsoft 365 license (enabled and warning). diff --git a/m365/graphing/m365_licenses.py b/m365/graphing/m365_licenses.py index 8b963bb..dc2ad88 100644 --- a/m365/graphing/m365_licenses.py +++ b/m365/graphing/m365_licenses.py @@ -34,6 +34,13 @@ UNIT_COUNTER = Unit(DecimalNotation(""), StrictPrecision(0)) UNIT_PERCENTAGE = Unit(DecimalNotation("%")) +metric_m365_licenses_available = Metric( + name="m365_licenses_available", + title=Title("Available"), + unit=UNIT_COUNTER, + color=Color.LIGHT_GRAY, +) + metric_m365_licenses_consumed = Metric( name="m365_licenses_consumed", title=Title("Consumed"), @@ -52,14 +59,14 @@ name="m365_licenses_enabled", title=Title("Enabled"), unit=UNIT_COUNTER, - color=Color.DARK_CYAN, + color=Color.PURPLE, ) -metric_m365_licenses_available = Metric( - name="m365_licenses_available", - title=Title("Available"), +metric_m365_licenses_total = Metric( + name="m365_licenses_total", + title=Title("Total (Enabled + Warning)"), unit=UNIT_COUNTER, - color=Color.LIGHT_GRAY, + color=Color.GREEN, ) metric_m365_licenses_warning = Metric( @@ -77,12 +84,12 @@ "m365_licenses_available", ], simple_lines=[ + "m365_licenses_total", "m365_licenses_enabled", "m365_licenses_warning", WarningOf("m365_licenses_consumed"), CriticalOf("m365_licenses_consumed"), ], - optional=["m365_licenses_warning"], ) graph_m365_license_usage = Graph( diff --git a/m365/libexec/agent_m365 b/m365/libexec/agent_m365 index 75bd5ef..ba0b30e 100644 --- a/m365/libexec/agent_m365 +++ b/m365/libexec/agent_m365 @@ -84,8 +84,47 @@ def get_access_token(tenant_id, app_id, app_secret, resource_scope): return access_token +def get_m365_licenses(token): + m365_licenses_url = ( + "https://graph.microsoft.com/v1.0/subscribedSkus" + "?$select=skuId,skuPartNumber,capabilityStatus,consumedUnits,prepaidUnits" + ) + + headers = {"Authorization": "Bearer " + token} + + try: + m365_licenses_response = requests.get(m365_licenses_url, headers=headers) + m365_licenses_response.raise_for_status() + except requests.exceptions.RequestException as err: + print(m365_licenses_response.text) + sys.stderr.write("CRITICAL | Failed to get m365 licenses\n") + sys.stderr.write(f"Error: {err}\n") + sys.exit(2) + + m365_licenses = m365_licenses_response.json().get("value", []) + + license_list = [] + for license in m365_licenses: + lic_units = license["prepaidUnits"] + if license["capabilityStatus"] in ["Enabled", "Warning"]: + license_dict = { + "lic_sku_id": license["skuId"], + "lic_sku_name": license["skuPartNumber"], + "lic_state": license["capabilityStatus"], + "lic_units_consumed": license["consumedUnits"], + "lic_units_enabled": lic_units["enabled"], + "lic_units_lockedout": lic_units["lockedOut"], + "lic_units_suspended": lic_units["suspended"], + "lic_units_warning": lic_units["warning"], + } + + license_list.append(license_dict) + + return license_list + + def get_m365_service_health(token): - m365_healt_overview_url = "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/" + m365_healt_overview_url = "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews" headers = {"Authorization": "Bearer " + token} @@ -95,7 +134,7 @@ def get_m365_service_health(token): except requests.exceptions.RequestException as err: sys.stderr.write("CRITICAL | Failed to get m365 service overview\n") sys.stderr.write(f"Error: {err}\n") - sys.exit(2) + sys.exit(3) m365_health_overview = m365_health_overview_response.json().get("value", []) @@ -112,7 +151,7 @@ def get_m365_service_health(token): except requests.exceptions.RequestException as err: sys.stderr.write("CRITICAL | Failed to get m365 service issues\n") sys.stderr.write(f"Error: {err}\n") - sys.exit(3) + sys.exit(4) m365_health_issues = m365_health_issues_response.json().get("value", []) @@ -123,19 +162,19 @@ def get_m365_service_health(token): for issue in m365_health_issues: if service_name == issue["service"]: issue_dict = { - "issue_title": issue.get("title"), - "issue_start": issue.get("startDateTime"), - "issue_feature": issue.get("feature"), "issue_classification": issue.get("classification"), + "issue_feature": issue.get("feature"), "issue_id": issue.get("id"), + "issue_start": issue.get("startDateTime"), + "issue_title": issue.get("title"), } issue_list.append(issue_dict) service_dict = { + "service_issues": issue_list, "service_name": service_name, "service_status": service.get("status"), - "service_issues": issue_list, } service_list.append(service_dict) @@ -143,45 +182,6 @@ def get_m365_service_health(token): return service_list -def get_m365_licenses(token): - m365_licenses_url = ( - "https://graph.microsoft.com/v1.0/subscribedSkus" - "?$select=skuId,skuPartNumber,capabilityStatus,consumedUnits,prepaidUnits" - ) - - headers = {"Authorization": "Bearer " + token} - - try: - m365_licenses_response = requests.get(m365_licenses_url, headers=headers) - m365_licenses_response.raise_for_status() - except requests.exceptions.RequestException as err: - print(m365_licenses_response.text) - sys.stderr.write("CRITICAL | Failed to get m365 licenses\n") - sys.stderr.write(f"Error: {err}\n") - sys.exit(4) - - m365_licenses = m365_licenses_response.json().get("value", []) - - license_list = [] - for license in m365_licenses: - lic_units = license["prepaidUnits"] - if license["capabilityStatus"] in ["Enabled", "Warning"]: - license_dict = { - "lic_sku_id": license["skuId"], - "lic_sku_name": license["skuPartNumber"], - "lic_state": license["capabilityStatus"], - "lic_units_consumed": license["consumedUnits"], - "lic_units_enabled": lic_units["enabled"], - "lic_units_lockedout": lic_units["lockedOut"], - "lic_units_suspended": lic_units["suspended"], - "lic_units_warning": lic_units["warning"], - } - - license_list.append(license_dict) - - return license_list - - def main(): args = parse_arguments() tenant_id = args.tenant_id @@ -195,15 +195,14 @@ def main(): token = get_access_token(tenant_id, app_id, app_secret, resource_scope) - # if not services_to_monitor or "m365_service_health" in services_to_monitor: - if "m365_service_health" in services_to_monitor: - print("<<>>") - print(json.dumps(get_m365_service_health(token))) - if "m365_licenses" in services_to_monitor: print("<<>>") print(json.dumps(get_m365_licenses(token))) + if "m365_service_health" in services_to_monitor: + print("<<>>") + print(json.dumps(get_m365_service_health(token))) + if __name__ == "__main__": main()