initial upload of version 3.1.1 which uses the new plugin API
This commit is contained in:
parent
0152860b55
commit
c0f4490b89
15
README.md
15
README.md
@ -1,16 +1,18 @@
|
|||||||
# Nextcloud CheckMK Special Agent
|
# Nextcloud CheckMK Special Agent
|
||||||
Monitors various aspects of Nextcloud instances like state, quota and disk usage of all users, number of apps with available updates, database php opcache hit rate and so on.
|
Monitors various aspects of Nextcloud instances like state, quota and disk usage of all users, number of apps with available updates, database php opcache hit rate and so on.
|
||||||
Gives additional information regarding versions of Nextcloud, database, number of storages and active users etc.
|
Gives additional information regarding versions of Nextcloud, database, number of storages and active users etc.
|
||||||
Tested with Nextcloud 25/26/27/28.
|
Tested only with Nextcloud versions 29/30
|
||||||
Tested only with MySQL/MariaDB as database backend.
|
Tested only with MariaDB as database backend
|
||||||
|
The whole plugin is now migrated to the new plugin API version, introduced with CheckMK 2.3
|
||||||
Feel free to report other working environments.
|
Feel free to report other working environments.
|
||||||
|
|
||||||
|
|
||||||
Upgrade from older MKPs (before 2.4.0):
|
Upgrade from older MKPs (before 3.1.1):
|
||||||
If you upgrade from a already installed version before 2.4.0, you have to re-create your rules for the "Nextcloud Server Information" (reason: massive parameter changes).
|
|
||||||
Steps to accomplish this without problems:
|
|
||||||
|
|
||||||
1. Take a screenshot of your settings in the above mentioned ruleset
|
If you upgrade from a already installed MKP version before 3.1.0, you have to re-create your rules for the "Nextcloud Server Information" (reason: some parameter changes combined with the migration to the new plugin API).
|
||||||
|
Hint: It is always a good idea to create screenshots from all other Nextcloud rules as well.
|
||||||
|
|
||||||
|
1. Take a screenshot of your settings in the above mentioned rulesets
|
||||||
2. Assure that you have access to the passwords/tokens you used within the current rules
|
2. Assure that you have access to the passwords/tokens you used within the current rules
|
||||||
3. Delete all rules for "Nextcloud Server Information"
|
3. Delete all rules for "Nextcloud Server Information"
|
||||||
4. Install and enable the new MKP
|
4. Install and enable the new MKP
|
||||||
@ -39,6 +41,7 @@ Version History:
|
|||||||
--
|
--
|
||||||
|Date|Version|Changes|
|
|Date|Version|Changes|
|
||||||
|----|-------|-------|
|
|----|-------|-------|
|
||||||
|
|2025/03/30|3.1.1|Migration to the new plugin API version finished, (hopefully) ready for CheckMK version 2.4
|
||||||
|2023/01/13|2.5.2|Repackaged only to set the minimum required version back to 2.1.0p1, due to serveral user requests|
|
|2023/01/13|2.5.2|Repackaged only to set the minimum required version back to 2.1.0p1, due to serveral user requests|
|
||||||
|2023/01/12|2.5.1|Added versions for apps with available updates|
|
|2023/01/12|2.5.1|Added versions for apps with available updates|
|
||||||
|2023/01/12|2.4.1|Removed Parameter "token", switched to parameter "app password" only|
|
|2023/01/12|2.4.1|Removed Parameter "token", switched to parameter "app password" only|
|
||||||
|
@ -1,82 +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_nextcloud_database(section):
|
|
||||||
yield(Service())
|
|
||||||
|
|
||||||
def check_nextcloud_database(params, section):
|
|
||||||
for key in section:
|
|
||||||
if key == "database":
|
|
||||||
opcache_hit_rate = section[key]["opcache_hit_rate"]
|
|
||||||
size = section[key]["size"]
|
|
||||||
type = section[key]["type"]
|
|
||||||
version = section[key]["version"]
|
|
||||||
levels = params["levels_database_opcache_hit_rate"]
|
|
||||||
# create graph for opcache hit rate
|
|
||||||
yield Metric("nc_database_opcache_hit_rate", opcache_hit_rate, levels=levels)
|
|
||||||
# create graph for database size
|
|
||||||
yield Metric("nc_database_size", size)
|
|
||||||
state = getStateLower(levels, opcache_hit_rate)
|
|
||||||
summary = f"PHP OPCache hit rate: {render.percent(opcache_hit_rate)}"
|
|
||||||
if opcache_hit_rate == 0:
|
|
||||||
state = State.UNKNOWN
|
|
||||||
summary = f"PHP OPCache hit rate: 0% - please check if opcache_get_status is enabled"
|
|
||||||
details = f"\nDatabase type: {type}\nDatabase version: {version}\nDatabase size: {render.bytes(size)}"
|
|
||||||
yield(Result(state=state, summary=summary, details=details))
|
|
||||||
|
|
||||||
def parse_nextcloud_database_section(string_table):
|
|
||||||
parsed_data = {
|
|
||||||
"database" : {}
|
|
||||||
}
|
|
||||||
params_list = [
|
|
||||||
"NC_Database_Type",
|
|
||||||
"NC_Database_Version",
|
|
||||||
"NC_Database_Size",
|
|
||||||
"NC_OPCache_Hit_Rate"
|
|
||||||
]
|
|
||||||
for line in string_table:
|
|
||||||
if line[0] in params_list:
|
|
||||||
param = line[0]
|
|
||||||
value = line[1]
|
|
||||||
if param == "NC_Database_Type":
|
|
||||||
parsed_data["database"]["type"] = value
|
|
||||||
elif param == "NC_Database_Version":
|
|
||||||
parsed_data["database"]["version"] = value
|
|
||||||
elif param == "NC_Database_Size":
|
|
||||||
parsed_data["database"]["size"] = int(value)
|
|
||||||
elif param == "NC_OPCache_Hit_Rate":
|
|
||||||
parsed_data["database"]["opcache_hit_rate"] = float(value)
|
|
||||||
return parsed_data
|
|
||||||
|
|
||||||
register.agent_section(
|
|
||||||
name="nextcloud_database",
|
|
||||||
parse_function=parse_nextcloud_database_section,
|
|
||||||
)
|
|
||||||
|
|
||||||
register.check_plugin(
|
|
||||||
name="nextcloud_database",
|
|
||||||
service_name="Nextcloud Database",
|
|
||||||
discovery_function=discover_nextcloud_database,
|
|
||||||
check_function=check_nextcloud_database,
|
|
||||||
check_default_parameters={
|
|
||||||
"levels_database_opcache_hit_rate": (99.7, 99.1),
|
|
||||||
},
|
|
||||||
check_ruleset_name="nextcloud_database",
|
|
||||||
)
|
|
@ -1,311 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import getopt
|
|
||||||
import sys
|
|
||||||
import requests
|
|
||||||
import urllib3
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from pprint import pprint
|
|
||||||
from requests.structures import CaseInsensitiveDict
|
|
||||||
from requests.auth import HTTPBasicAuth
|
|
||||||
import cmk.utils.password_store
|
|
||||||
|
|
||||||
def showUsage():
|
|
||||||
sys.stderr.write("""CheckMK Nextcloud Special Agent
|
|
||||||
|
|
||||||
USAGE: agent_nextcloud_info -u [username] -p [password]
|
|
||||||
OR
|
|
||||||
agent_nextcloud_info -h
|
|
||||||
|
|
||||||
OPTIONS:
|
|
||||||
-H, --hostname Hostname (FQDN or IP) of Nextcloud server
|
|
||||||
-u, --username Username
|
|
||||||
-p, --password App Password
|
|
||||||
-P, --port Port
|
|
||||||
-f, --folder Subfolder if not installed in web root
|
|
||||||
--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
|
|
||||||
""")
|
|
||||||
|
|
||||||
# set this to true to produce debug output (this clutters the agent output)
|
|
||||||
# be aware: activating this flag logs very sensitive information to debug files in ~/tmp
|
|
||||||
# !!DO NOT FORGET to delete these files after debugging is done!!
|
|
||||||
|
|
||||||
DEBUG = False
|
|
||||||
|
|
||||||
nc_api_endpoint = "ocs/v2.php/apps/serverinfo/api/v1/info?format=json"
|
|
||||||
nc_api_endpoint_all_users = "ocs/v1.php/cloud/users?format=json"
|
|
||||||
nc_api_endpoint_user = "ocs/v1.php/cloud/users"
|
|
||||||
|
|
||||||
opt_hostname = ""
|
|
||||||
opt_username = ""
|
|
||||||
opt_password = ""
|
|
||||||
opt_port = ""
|
|
||||||
opt_folder = ""
|
|
||||||
opt_no_https = False
|
|
||||||
opt_no_cert_check = False
|
|
||||||
|
|
||||||
short_options = 'hH:u:p:P:f:'
|
|
||||||
long_options = [
|
|
||||||
'hostname=', 'username=', 'password=', 'port=', 'folder=', 'no-https=', 'no-cert-check=', 'help'
|
|
||||||
]
|
|
||||||
|
|
||||||
def logDebug(line):
|
|
||||||
if DEBUG:
|
|
||||||
home_path = os.getenv("HOME")
|
|
||||||
tmp_path = f"{home_path}/tmp"
|
|
||||||
help_file = f"{tmp_path}/nextcloud_{opt_hostname}_{opt_port}_debug.txt"
|
|
||||||
with open(help_file, "a") as file:
|
|
||||||
file.write(line)
|
|
||||||
|
|
||||||
|
|
||||||
def getOptions():
|
|
||||||
global opt_hostname
|
|
||||||
global opt_username
|
|
||||||
global opt_password
|
|
||||||
global opt_port
|
|
||||||
global opt_folder
|
|
||||||
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 ['-u', '--username']:
|
|
||||||
opt_username = arg
|
|
||||||
elif opt in ['-p', '--password']:
|
|
||||||
opt_password = arg
|
|
||||||
elif opt in ['-P', '--port']:
|
|
||||||
opt_port = arg
|
|
||||||
elif opt in ['-f', '--folder']:
|
|
||||||
opt_folder = arg
|
|
||||||
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)
|
|
||||||
logDebug(f"getOptions - Number of Arguments: {len(sys.argv)}, Argument List: {str(sys.argv)}\n")
|
|
||||||
|
|
||||||
def showOptions():
|
|
||||||
print(f"Hostname: {opt_hostname}")
|
|
||||||
print(f"Username: {opt_username}")
|
|
||||||
print(f"Password: {opt_password}")
|
|
||||||
print(f"Port: {opt_port}")
|
|
||||||
print(f"Folder: {opt_folder}")
|
|
||||||
print(f"No HTTPS: {opt_no_https}")
|
|
||||||
print(f"No TLS Check: {opt_no_cert_check}")
|
|
||||||
logDebug(f"showOptions - Hostname: {opt_hostname}, Port: {opt_port}, No HTTPS: {opt_no_https}, No Cert Check: {opt_no_cert_check}\n")
|
|
||||||
|
|
||||||
def createUrl(endpoint, hostname, protocol, port, folder):
|
|
||||||
# these parameters are needed, otherwise no information about updates regarding apps and Nextcloud itself are reported (since version 28)
|
|
||||||
params = "skipApps=false&skipUpdate=false"
|
|
||||||
if folder == "":
|
|
||||||
url = f"{protocol}://{hostname}:{port}/{endpoint}"
|
|
||||||
else:
|
|
||||||
url = f"{protocol}://{hostname}:{port}/{folder}/{endpoint}"
|
|
||||||
if endpoint == nc_api_endpoint:
|
|
||||||
url = f"{url}&{params}"
|
|
||||||
logDebug(f"createUrl - Data URL: {url}\n")
|
|
||||||
return url
|
|
||||||
|
|
||||||
def createUrlUser(user, endpoint, hostname, protocol, port, folder):
|
|
||||||
params = "format=json"
|
|
||||||
if folder == "":
|
|
||||||
url = f"{protocol}://{hostname}:{port}/{endpoint}/{user}?{params}"
|
|
||||||
else:
|
|
||||||
url = f"{protocol}://{hostname}:{port}/{folder}/{endpoint}/{user}?{params}"
|
|
||||||
logDebug(f"createUrlUser - User URL: {url}\n")
|
|
||||||
return url
|
|
||||||
|
|
||||||
def getSession(username, secret):
|
|
||||||
session = requests.session()
|
|
||||||
session.cookies.set("SameSite", "Strict")
|
|
||||||
session.auth = (username, secret)
|
|
||||||
session.headers['Accept'] = "application/json"
|
|
||||||
return session
|
|
||||||
|
|
||||||
def getData(session, url, verify):
|
|
||||||
data = {}
|
|
||||||
headers = CaseInsensitiveDict()
|
|
||||||
headers["Accept"] = "application/json"
|
|
||||||
response = session.get(url, headers=headers, verify=verify)
|
|
||||||
status = response.status_code
|
|
||||||
if (status == 200):
|
|
||||||
jsdata = response.text
|
|
||||||
data = json.loads(jsdata) # returns a dictionary
|
|
||||||
return data
|
|
||||||
elif (status == 503):
|
|
||||||
# this code is reported when maintenance mode is on
|
|
||||||
sys.stderr.write(f"Request response code is {response.status_code} with URL {url}, maybe maintenance mode is on?\n")
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
sys.stderr.write(f"Request response code is {response.status_code} with URL {url}\n")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def getDataAllUsers(session, url, verify):
|
|
||||||
headers = CaseInsensitiveDict()
|
|
||||||
headers["Accept"] = "application/json"
|
|
||||||
headers["OCS-APIRequest"] = "true"
|
|
||||||
response = session.get(url, headers=headers, verify=verify)
|
|
||||||
status = response.status_code
|
|
||||||
if (status == 200):
|
|
||||||
jsdata = response.text
|
|
||||||
data = json.loads(jsdata) # returns a dictionary
|
|
||||||
return data
|
|
||||||
else:
|
|
||||||
sys.stderr.write(f"Request response code is {response.status_code} with URL {url}\n")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def getDataUser(session, url, verify):
|
|
||||||
headers = CaseInsensitiveDict()
|
|
||||||
headers["Accept"] = "application/json"
|
|
||||||
headers["OCS-APIRequest"] = "true"
|
|
||||||
response = session.get(url, headers=headers, verify=verify)
|
|
||||||
status = response.status_code
|
|
||||||
if (status == 200):
|
|
||||||
jsdata = response.text
|
|
||||||
data = json.loads(jsdata) # returns a dictionary
|
|
||||||
return data
|
|
||||||
else:
|
|
||||||
sys.stderr.write(f"Request response code is {response.status_code} with URL {url}\n")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def doCmkOutput(data):
|
|
||||||
apps_with_updates_available = {}
|
|
||||||
str_apps_with_updates_available = ""
|
|
||||||
print("<<<nextcloud_info:sep(59)>>>")
|
|
||||||
print(f"NC_Version;{data['ocs']['data']['nextcloud']['system']['version']}")
|
|
||||||
print(f"NC_Freespace;{data['ocs']['data']['nextcloud']['system']['freespace']}")
|
|
||||||
print(f"NC_Status;{data['ocs']['meta']['status']}")
|
|
||||||
# This update info is available only from version 28 onwards, so the key "update" does not exist in all versions before
|
|
||||||
try:
|
|
||||||
print(f"NC_Last_Update;{data['ocs']['data']['nextcloud']['system']['update']['lastupdatedat']}")
|
|
||||||
print(f"NC_Update_Available;{data['ocs']['data']['nextcloud']['system']['update']['available']}")
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
print(f"NC_Num_Users;{data['ocs']['data']['nextcloud']['storage']['num_users']}")
|
|
||||||
print(f"NC_Num_Files;{data['ocs']['data']['nextcloud']['storage']['num_files']}")
|
|
||||||
print(f"NC_Num_Shares;{data['ocs']['data']['nextcloud']['shares']['num_shares']}")
|
|
||||||
print(f"NC_Num_Storages;{data['ocs']['data']['nextcloud']['storage']['num_storages']}")
|
|
||||||
print(f"NC_Num_Storages_Home;{data['ocs']['data']['nextcloud']['storage']['num_storages_home']}")
|
|
||||||
print(f"NC_Num_Storages_Local;{data['ocs']['data']['nextcloud']['storage']['num_storages_local']}")
|
|
||||||
print(f"NC_Num_Storages_Other;{data['ocs']['data']['nextcloud']['storage']['num_storages_other']}")
|
|
||||||
# Workaround for Nextcloud 28.0.1 (KeyError "apps")
|
|
||||||
try:
|
|
||||||
print(f"NC_Num_Apps_Installed;{data['ocs']['data']['nextcloud']['system']['apps']['num_installed']}")
|
|
||||||
print(f"NC_Num_Apps_Updates_Available;{data['ocs']['data']['nextcloud']['system']['apps']['num_updates_available']}")
|
|
||||||
apps_with_updates_available = data['ocs']['data']['nextcloud']['system']['apps']['app_updates']
|
|
||||||
if apps_with_updates_available:
|
|
||||||
for app, version in apps_with_updates_available.items():
|
|
||||||
str_apps_with_updates_available = str_apps_with_updates_available + app + "/" + version + " "
|
|
||||||
print(f"NC_Apps_With_Updates_Available;{str_apps_with_updates_available}")
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
print(f"NC_Active_Users_Last_5Min;{data['ocs']['data']['activeUsers']['last5minutes']}")
|
|
||||||
print(f"NC_Active_Users_Last_1Hour;{data['ocs']['data']['activeUsers']['last1hour']}")
|
|
||||||
print(f"NC_Active_Users_Last_1Day;{data['ocs']['data']['activeUsers']['last24hours']}")
|
|
||||||
print(f"NC_Webserver;{data['ocs']['data']['server']['webserver']}")
|
|
||||||
print(f"NC_PHP_Version;{data['ocs']['data']['server']['php']['version']}")
|
|
||||||
|
|
||||||
print("<<<nextcloud_database:sep(59)>>>")
|
|
||||||
print(f"NC_Database_Type;{data['ocs']['data']['server']['database']['type']}")
|
|
||||||
print(f"NC_Database_Version;{data['ocs']['data']['server']['database']['version']}")
|
|
||||||
print(f"NC_Database_Size;{data['ocs']['data']['server']['database']['size']}")
|
|
||||||
# opcache entry does not exist if opcache_get_status is disabled by server settings
|
|
||||||
# thanks to Marcus Klein from Iteratio to report (and solve!) this bug
|
|
||||||
if (data['ocs']['data']['server']['php']['opcache']):
|
|
||||||
print(f"NC_OPCache_Hit_Rate;{data['ocs']['data']['server']['php']['opcache']['opcache_statistics']['opcache_hit_rate']}")
|
|
||||||
else:
|
|
||||||
print(f"NC_OPCache_Hit_Rate;0")
|
|
||||||
|
|
||||||
def doCmkOutputAllUsers(session, data, verify, hostname, protocol, port, folder):
|
|
||||||
print("<<<nextcloud_users:sep(59)>>>")
|
|
||||||
for user in data['ocs']['data']['users']:
|
|
||||||
nc_url = createUrlUser(user, nc_api_endpoint_user, hostname, protocol, port, folder)
|
|
||||||
user_data = getDataUser(session, nc_url, verify)
|
|
||||||
userid = user_data['ocs']['data']['id']
|
|
||||||
displayname = user_data['ocs']['data']['displayname']
|
|
||||||
lastlogin = int(user_data['ocs']['data']['lastLogin'])
|
|
||||||
if lastlogin == 0:
|
|
||||||
# user has never logged in
|
|
||||||
quota_free = -1
|
|
||||||
quota_quota = -1
|
|
||||||
quota_relative = -1
|
|
||||||
quota_total = -1
|
|
||||||
quota_used = -1
|
|
||||||
else:
|
|
||||||
quota_free = user_data['ocs']['data']['quota']['free']
|
|
||||||
# quota_quota == -3 --> unlimited
|
|
||||||
quota_quota = user_data['ocs']['data']['quota']['quota']
|
|
||||||
# quota_relative = used * 100 / (free + used)
|
|
||||||
quota_relative = user_data['ocs']['data']['quota']['relative']
|
|
||||||
quota_total = user_data['ocs']['data']['quota']['total']
|
|
||||||
quota_used = user_data['ocs']['data']['quota']['used']
|
|
||||||
print(f"{userid};{displayname};{lastlogin};{quota_free};{quota_quota};{quota_relative};{quota_total};{quota_used}")
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# replace password from pwd store
|
|
||||||
cmk.utils.password_store.replace_passwords()
|
|
||||||
getOptions()
|
|
||||||
if (opt_hostname == ""):
|
|
||||||
sys.stderr.write(f"No hostname given.\n")
|
|
||||||
showUsage()
|
|
||||||
sys.exit(1)
|
|
||||||
if (opt_username == ""):
|
|
||||||
sys.stderr.write(f"No username given.\n")
|
|
||||||
showUsage()
|
|
||||||
sys.exit(1)
|
|
||||||
if (opt_password == ""):
|
|
||||||
sys.stderr.write(f"No password given.\n")
|
|
||||||
showUsage()
|
|
||||||
sys.exit(1)
|
|
||||||
if (opt_no_cert_check):
|
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
||||||
verify = False
|
|
||||||
else:
|
|
||||||
verify = True
|
|
||||||
if (opt_port == ""):
|
|
||||||
if (opt_no_https):
|
|
||||||
protocol = "http"
|
|
||||||
port = "80"
|
|
||||||
else:
|
|
||||||
protocol = "https"
|
|
||||||
port = "443"
|
|
||||||
else:
|
|
||||||
if (opt_no_https):
|
|
||||||
protocol = "http"
|
|
||||||
else:
|
|
||||||
protocol = "https"
|
|
||||||
port = opt_port
|
|
||||||
if DEBUG:
|
|
||||||
showOptions()
|
|
||||||
if (protocol == "http" and port == "443"):
|
|
||||||
sys.stderr.write(f"Combining HTTP with port 443 is not supported.\n")
|
|
||||||
sys.exit(1)
|
|
||||||
if (protocol == "https" and port == "80"):
|
|
||||||
sys.stderr.write(f"Combining HTTPS with port 80 is not supported.\n")
|
|
||||||
sys.exit(1)
|
|
||||||
pwd = opt_password
|
|
||||||
|
|
||||||
# create session
|
|
||||||
session = getSession(opt_username, pwd)
|
|
||||||
nc_url = createUrl(nc_api_endpoint, opt_hostname, protocol, port, opt_folder)
|
|
||||||
nc_data = getData(session, nc_url, verify)
|
|
||||||
doCmkOutput(nc_data)
|
|
||||||
nc_url = createUrl(nc_api_endpoint_all_users, opt_hostname, protocol, port, opt_folder)
|
|
||||||
nc_data = getDataAllUsers(session, nc_url, verify)
|
|
||||||
doCmkOutputAllUsers(session, nc_data, verify, opt_hostname, protocol, port, opt_folder)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,16 +0,0 @@
|
|||||||
title: Nextcloud: Database information
|
|
||||||
agents: linux
|
|
||||||
catalog: unsorted
|
|
||||||
license: GPL
|
|
||||||
distribution: check_mk
|
|
||||||
description:
|
|
||||||
Works with Nextcloud version 25/26/27 (use at your own risk with lower versions).
|
|
||||||
Tested only with mysql/mariab as underlying database.
|
|
||||||
You may use a username/password or username/token combination to get access to the Nextcloud API.
|
|
||||||
You can create this token within the personal settings of an administrative user in Nextcloud.
|
|
||||||
Got to security settings and create a new app password, use this for the token.
|
|
||||||
The user must not be secured with 2FA.
|
|
||||||
Shows several information about a Nextcloud database (type/version/size).
|
|
||||||
The check will raise WARN/CRIT if Database PHP OPcache hit rate is below the configurable levels.
|
|
||||||
inventory:
|
|
||||||
one service is created (with several details)
|
|
@ -1,13 +0,0 @@
|
|||||||
def agent_nextcloud_arguments(params, hostname, ipaddress):
|
|
||||||
return [
|
|
||||||
"--hostname", params["hostname"],
|
|
||||||
"--username", params["username"],
|
|
||||||
"--password", passwordstore_get_cmdline("%s", params["password"]),
|
|
||||||
"--port", params["port"],
|
|
||||||
"--folder", params["folder"],
|
|
||||||
"--no-https", params["no_https"],
|
|
||||||
"--no-cert-check", params["no_cert_check"],
|
|
||||||
ipaddress,
|
|
||||||
]
|
|
||||||
|
|
||||||
special_agent_info['nextcloud'] = agent_nextcloud_arguments
|
|
@ -1,135 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from cmk.gui.i18n import _
|
|
||||||
from cmk.gui.plugins.metrics import (
|
|
||||||
metric_info,
|
|
||||||
graph_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
metric_info["nc_num_users"] = {
|
|
||||||
"title": _("Number of Users"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "44/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_num_shares"] = {
|
|
||||||
"title": _("Number of Shares"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "44/b",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_num_storages"] = {
|
|
||||||
"title": _("Number of Storages"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "42/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_num_storages_home"] = {
|
|
||||||
"title": _("Number of Home Storages"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "44/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_num_storages_local"] = {
|
|
||||||
"title": _("Number of Local Storages"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "44/b",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_num_storages_other"] = {
|
|
||||||
"title": _("Number of Other Storages"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "42/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_num_files"] = {
|
|
||||||
"title": _("Number of Files"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "42/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_num_apps_installed"] = {
|
|
||||||
"title": _("Number of installed Apps"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "43/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_apps_with_updates_available"] = {
|
|
||||||
"title": _("Number of installed Apps with Updates Available"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "24/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_active_users_last_5min"] = {
|
|
||||||
"title": _("Number of Active Users in the last 5 minutes"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "24/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_active_users_last_1hour"] = {
|
|
||||||
"title": _("Number of Active Users in the last 1 hour"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "41/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_active_users_last_1day"] = {
|
|
||||||
"title": _("Number of Active Users in the last 1 day"),
|
|
||||||
"unit": "count",
|
|
||||||
"color": "42/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_users_free_space"] = {
|
|
||||||
"title": _("Free Space of User"),
|
|
||||||
"unit": "bytes",
|
|
||||||
"color": "22/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_free_space"] = {
|
|
||||||
"title": _("Free Space on Disk"),
|
|
||||||
"unit": "bytes",
|
|
||||||
"color": "22/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_database_size"] = {
|
|
||||||
"title": _("Database Size"),
|
|
||||||
"unit": "bytes",
|
|
||||||
"color": "24/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_database_opcache_hit_rate"] = {
|
|
||||||
"title": _("Database PHP OPCache Hit Rate"),
|
|
||||||
"unit": "%",
|
|
||||||
"color": "24/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
metric_info["nc_users_quota_used"] = {
|
|
||||||
"title": _("Quota used"),
|
|
||||||
"unit": "%",
|
|
||||||
"color": "16/a",
|
|
||||||
}
|
|
||||||
|
|
||||||
graph_info["number_of_users_shares_storages_combined"] = {
|
|
||||||
"title": _("Number of Users, Shares and Storages"),
|
|
||||||
"metrics": [
|
|
||||||
("nc_num_users", "area"),
|
|
||||||
("nc_num_shares", "stack"),
|
|
||||||
("nc_num_storages", "stack"),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
graph_info["number_of_storage_types_combined"] = {
|
|
||||||
"title": _("Number of Storage Types"),
|
|
||||||
"metrics": [
|
|
||||||
("nc_num_storages_home", "area"),
|
|
||||||
("nc_num_storages_local", "stack"),
|
|
||||||
("nc_num_storages_other", "stack"),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
graph_info["number_of_active_users_combined"] = {
|
|
||||||
"title": _("Number of Active Users"),
|
|
||||||
"metrics": [
|
|
||||||
("nc_active_users_last_5min", "area"),
|
|
||||||
("nc_active_users_last_1hour", "stack"),
|
|
||||||
("nc_active_users_last_1day", "stack"),
|
|
||||||
],
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from cmk.gui.plugins.metrics import perfometer_info
|
|
||||||
|
|
||||||
perfometer_info.append({
|
|
||||||
"type": "stacked",
|
|
||||||
"perfometers": [
|
|
||||||
{
|
|
||||||
"type": "linear",
|
|
||||||
"segments": ["nc_database_opcache_hit_rate"],
|
|
||||||
"total": 100.0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
perfometer_info.append({
|
|
||||||
"type": "stacked",
|
|
||||||
"perfometers": [
|
|
||||||
{
|
|
||||||
"type": "linear",
|
|
||||||
"segments": ["nc_database_size"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
perfometer_info.append({
|
|
||||||
"type": "stacked",
|
|
||||||
"perfometers": [
|
|
||||||
{
|
|
||||||
"type": "linear",
|
|
||||||
"segments": ["nc_users_quota_used"],
|
|
||||||
"total": 100.0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
@ -1,43 +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_nextcloud_database():
|
|
||||||
return Dictionary(
|
|
||||||
elements=[
|
|
||||||
("levels_database_opcache_hit_rate", Tuple(
|
|
||||||
title=_("Nextcloud levels for database php opcache hit rate"),
|
|
||||||
elements=[
|
|
||||||
Percentage(
|
|
||||||
title=_("Warning below"),
|
|
||||||
default_value=99.7,
|
|
||||||
),
|
|
||||||
Percentage(
|
|
||||||
title=_("Critical below"),
|
|
||||||
default_value=99.1,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
rulespec_registry.register(
|
|
||||||
CheckParameterRulespecWithoutItem(
|
|
||||||
check_group_name="nextcloud_database",
|
|
||||||
group=RulespecGroupCheckParametersApplications,
|
|
||||||
match_type="dict",
|
|
||||||
parameter_valuespec=_parameter_spec_nextcloud_database,
|
|
||||||
title=lambda: _("Nextcloud Database"),
|
|
||||||
)
|
|
||||||
)
|
|
@ -1,74 +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_nextcloud_info():
|
|
||||||
return Dictionary(
|
|
||||||
elements=[
|
|
||||||
("levels_apps_with_updates_available", Tuple(
|
|
||||||
title=_("Nextcloud number of installed apps with updates available"),
|
|
||||||
elements=[
|
|
||||||
Integer(
|
|
||||||
title=_("Warning at"),
|
|
||||||
default_value=1,
|
|
||||||
),
|
|
||||||
Integer(
|
|
||||||
title=_("Critical at"),
|
|
||||||
default_value=2,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
("levels_free_space", Tuple(
|
|
||||||
title=_("Nextcloud levels for free disk space overall"),
|
|
||||||
elements=[
|
|
||||||
Float(
|
|
||||||
title=_("Warning below"),
|
|
||||||
default_value=8.0,
|
|
||||||
unit="GBytes",
|
|
||||||
),
|
|
||||||
Float(
|
|
||||||
title=_("Critical below"),
|
|
||||||
default_value=4.0,
|
|
||||||
unit="GBytes",
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
("levels_number_of_files", Tuple(
|
|
||||||
title=_("Nextcloud number of files"),
|
|
||||||
elements=[
|
|
||||||
Integer(
|
|
||||||
title=_("Warning at"),
|
|
||||||
default_value=100000,
|
|
||||||
size=32,
|
|
||||||
),
|
|
||||||
Integer(
|
|
||||||
title=_("Critical at"),
|
|
||||||
default_value=250000,
|
|
||||||
size=32,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
rulespec_registry.register(
|
|
||||||
CheckParameterRulespecWithoutItem(
|
|
||||||
check_group_name="nextcloud_info",
|
|
||||||
group=RulespecGroupCheckParametersApplications,
|
|
||||||
match_type="dict",
|
|
||||||
parameter_valuespec=_parameter_spec_nextcloud_info,
|
|
||||||
title=lambda: _("Nextcloud Info"),
|
|
||||||
)
|
|
||||||
)
|
|
@ -1,60 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from cmk.gui.i18n import _
|
|
||||||
from cmk.gui.plugins.wato.special_agents.common import RulespecGroupDatasourceProgramsApps
|
|
||||||
from cmk.gui.plugins.wato.utils import (
|
|
||||||
HostRulespec,
|
|
||||||
Rulespec,
|
|
||||||
IndividualOrStoredPassword,
|
|
||||||
rulespec_registry,
|
|
||||||
)
|
|
||||||
from cmk.gui.valuespec import (
|
|
||||||
Dictionary,
|
|
||||||
ListChoice,
|
|
||||||
Checkbox,
|
|
||||||
TextAscii,
|
|
||||||
Password,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _factory_default_special_agent_nextcloud():
|
|
||||||
return Rulespec.FACTORY_DEFAULT_UNUSED
|
|
||||||
|
|
||||||
def _valuespec_special_agent_nextcloud():
|
|
||||||
return Dictionary(
|
|
||||||
title=_("Nextcloud Server Information"),
|
|
||||||
help = _("Checking Nextcloud servers via API"),
|
|
||||||
elements=[
|
|
||||||
("hostname", TextAscii(title=_("Hostname"),
|
|
||||||
allow_empty=False,
|
|
||||||
size = 40,
|
|
||||||
help=_("Hostname of Nextcloud server (bare FQDN or IP), mandatory, eg. nextcloud.yourdomain.tld"))),
|
|
||||||
("username", TextAscii(title=_("Username"),
|
|
||||||
size = 40,
|
|
||||||
allow_empty=False,
|
|
||||||
help=_("Username with administrative rights, mandatory"))),
|
|
||||||
("password", IndividualOrStoredPassword(title=_("App Password"),
|
|
||||||
size = 40,
|
|
||||||
allow_empty=False,
|
|
||||||
help=_("Specify app password, mandatory, use Personal Settings|Security|Devices and Sessions within the NC UI to create one for the given user"))),
|
|
||||||
("port", TextAscii(title=_("Port"),
|
|
||||||
allow_empty=True,
|
|
||||||
help=_("Specify port if not listening to HTTP(S), optional"))),
|
|
||||||
("folder", TextAscii(title=_("Folder"),
|
|
||||||
allow_empty=True,
|
|
||||||
help=_("Specify subfolder if your Nextcloud instance is not installed in the web root, no trailing/leading slashes, optional"))),
|
|
||||||
("no_https", Checkbox(title=_("Disable HTTPS"),
|
|
||||||
help=_("Activate to disable 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(
|
|
||||||
factory_default = _factory_default_special_agent_nextcloud(),
|
|
||||||
group = RulespecGroupDatasourceProgramsApps,
|
|
||||||
name = "special_agents:nextcloud",
|
|
||||||
valuespec = _valuespec_special_agent_nextcloud,
|
|
||||||
)
|
|
||||||
)
|
|
@ -1,68 +0,0 @@
|
|||||||
from cmk.gui.i18n import _
|
|
||||||
from cmk.gui.plugins.wato import (
|
|
||||||
CheckParameterRulespecWithItem,
|
|
||||||
rulespec_registry,
|
|
||||||
RulespecGroupCheckParametersApplications
|
|
||||||
)
|
|
||||||
|
|
||||||
from cmk.gui.valuespec import (
|
|
||||||
Dictionary,
|
|
||||||
ListChoice,
|
|
||||||
Tuple,
|
|
||||||
Percentage,
|
|
||||||
Integer,
|
|
||||||
TextAscii,
|
|
||||||
Float,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _item_spec_nextcloud_users():
|
|
||||||
return TextAscii(
|
|
||||||
title=_("User ID")
|
|
||||||
)
|
|
||||||
|
|
||||||
def _parameter_spec_nextcloud_users():
|
|
||||||
return Dictionary(
|
|
||||||
elements=[
|
|
||||||
("levels_users_quota_used", Tuple(
|
|
||||||
title=_("Nextcloud levels for quota usage of users"),
|
|
||||||
elements=[
|
|
||||||
Percentage(
|
|
||||||
title=_("Warning at"),
|
|
||||||
default_value=65.0,
|
|
||||||
unit="%",
|
|
||||||
),
|
|
||||||
Percentage(
|
|
||||||
title=_("Critical at"),
|
|
||||||
default_value=85.0,
|
|
||||||
unit="%",
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
("levels_users_free_space", Tuple(
|
|
||||||
title=_("Nextcloud levels for free disk space of users"),
|
|
||||||
elements=[
|
|
||||||
Float(
|
|
||||||
title=_("Warning below"),
|
|
||||||
default_value=256.0,
|
|
||||||
unit="MBytes",
|
|
||||||
),
|
|
||||||
Float(
|
|
||||||
title=_("Critical below"),
|
|
||||||
default_value=128.0,
|
|
||||||
unit="MBytes",
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
rulespec_registry.register(
|
|
||||||
CheckParameterRulespecWithItem(
|
|
||||||
check_group_name="nextcloud_users",
|
|
||||||
group=RulespecGroupCheckParametersApplications,
|
|
||||||
match_type="dict",
|
|
||||||
item_spec=_item_spec_nextcloud_users,
|
|
||||||
parameter_valuespec=_parameter_spec_nextcloud_users,
|
|
||||||
title=lambda: _("Nextcloud Users"),
|
|
||||||
)
|
|
||||||
)
|
|
BIN
mkp/.DS_Store
vendored
Normal file
BIN
mkp/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
mkp/Nextcloud-3.1.1.mkp
Normal file
BIN
mkp/Nextcloud-3.1.1.mkp
Normal file
Binary file not shown.
105
nextcloud/agent_based/nextcloud_database.py
Normal file
105
nextcloud/agent_based/nextcloud_database.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# pylint: disable=missing-module-docstring, unused-argument, missing-function-docstring
|
||||||
|
# pylint: disable=line-too-long
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import NotRequired, TypedDict
|
||||||
|
|
||||||
|
from cmk.agent_based.v2 import (
|
||||||
|
AgentSection,
|
||||||
|
CheckPlugin,
|
||||||
|
CheckResult,
|
||||||
|
Metric,
|
||||||
|
render,
|
||||||
|
Result,
|
||||||
|
Service,
|
||||||
|
State,
|
||||||
|
StringTable,
|
||||||
|
DiscoveryResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _ItemData(TypedDict):
|
||||||
|
dbtype: NotRequired[str]
|
||||||
|
version: NotRequired[str]
|
||||||
|
size: NotRequired[int]
|
||||||
|
opcache_hit_rate: NotRequired[float]
|
||||||
|
|
||||||
|
|
||||||
|
Section = Mapping[str, _ItemData]
|
||||||
|
|
||||||
|
|
||||||
|
def get_state_lower(levels, value):
|
||||||
|
warn, crit = levels
|
||||||
|
if value < crit:
|
||||||
|
return State.CRIT
|
||||||
|
if value < warn:
|
||||||
|
return State.WARN
|
||||||
|
return State.OK
|
||||||
|
|
||||||
|
|
||||||
|
def discover_nextcloud_database(section) -> DiscoveryResult:
|
||||||
|
yield Service()
|
||||||
|
|
||||||
|
|
||||||
|
def check_nextcloud_database(params, section) -> CheckResult:
|
||||||
|
for key in section:
|
||||||
|
if key == "database":
|
||||||
|
opcache_hit_rate = section[key]["opcache_hit_rate"]
|
||||||
|
size = section[key]["size"]
|
||||||
|
dbtype = section[key]["dbtype"]
|
||||||
|
version = section[key]["version"]
|
||||||
|
_level_type, levels = params["levels_database_opcache_hit_rate"]
|
||||||
|
# create graph for opcache hit rate
|
||||||
|
yield Metric(
|
||||||
|
"nc_database_opcache_hit_rate", opcache_hit_rate, levels=levels
|
||||||
|
)
|
||||||
|
# create graph for database size
|
||||||
|
yield Metric("nc_database_size", size)
|
||||||
|
state = get_state_lower(levels, opcache_hit_rate)
|
||||||
|
summary = f"PHP OPCache hit rate: {render.percent(opcache_hit_rate)}"
|
||||||
|
if opcache_hit_rate == 0:
|
||||||
|
state = State.UNKNOWN
|
||||||
|
summary = "PHP OPCache hit rate: 0% - please check if opcache_get_status is enabled"
|
||||||
|
details = f"\nDatabase type: {dbtype}\nDatabase version: {version}\nDatabase size: {render.bytes(size)}"
|
||||||
|
yield Result(state=state, summary=summary, details=details)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_nextcloud_database(string_table: StringTable) -> Section:
|
||||||
|
parsed_data = {"database": {}}
|
||||||
|
params_list = [
|
||||||
|
"NC_Database_Type",
|
||||||
|
"NC_Database_Version",
|
||||||
|
"NC_Database_Size",
|
||||||
|
"NC_OPCache_Hit_Rate",
|
||||||
|
]
|
||||||
|
for line in string_table:
|
||||||
|
if line[0] in params_list:
|
||||||
|
param = line[0]
|
||||||
|
value = line[1]
|
||||||
|
if param == "NC_Database_Type":
|
||||||
|
parsed_data["database"]["dbtype"] = value
|
||||||
|
elif param == "NC_Database_Version":
|
||||||
|
parsed_data["database"]["version"] = value
|
||||||
|
elif param == "NC_Database_Size":
|
||||||
|
parsed_data["database"]["size"] = int(value)
|
||||||
|
elif param == "NC_OPCache_Hit_Rate":
|
||||||
|
parsed_data["database"]["opcache_hit_rate"] = float(value)
|
||||||
|
return parsed_data
|
||||||
|
|
||||||
|
|
||||||
|
agent_section_nextcloud_database = AgentSection(
|
||||||
|
name="nextcloud_database",
|
||||||
|
parse_function=parse_nextcloud_database,
|
||||||
|
)
|
||||||
|
|
||||||
|
check_plugin_nextcloud_database = CheckPlugin(
|
||||||
|
name="nextcloud_database",
|
||||||
|
service_name="Nextcloud Database",
|
||||||
|
discovery_function=discover_nextcloud_database,
|
||||||
|
check_function=check_nextcloud_database,
|
||||||
|
check_default_parameters={
|
||||||
|
"levels_database_opcache_hit_rate": ("fixed", (95.0, 85.0)),
|
||||||
|
},
|
||||||
|
check_ruleset_name="nextcloud_database",
|
||||||
|
)
|
@ -1,10 +1,24 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from pprint import pprint
|
# pylint: disable=missing-module-docstring, unused-argument, missing-function-docstring
|
||||||
|
# pylint: disable=line-too-long, too-many-branches, too-many-locals, too-many-statements
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from .agent_based_api.v1 import register, render, Service, Result, State, Metric
|
|
||||||
|
from cmk.agent_based.v2 import (
|
||||||
|
AgentSection,
|
||||||
|
CheckPlugin,
|
||||||
|
CheckResult,
|
||||||
|
Metric,
|
||||||
|
render,
|
||||||
|
Result,
|
||||||
|
Service,
|
||||||
|
State,
|
||||||
|
StringTable,
|
||||||
|
DiscoveryResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def getStateUpper(levels, value):
|
def get_state_upper(levels, value):
|
||||||
warn, crit = levels
|
warn, crit = levels
|
||||||
if value >= crit:
|
if value >= crit:
|
||||||
return State.CRIT
|
return State.CRIT
|
||||||
@ -13,7 +27,7 @@ def getStateUpper(levels, value):
|
|||||||
return State.OK
|
return State.OK
|
||||||
|
|
||||||
|
|
||||||
def getStateLower(levels, value):
|
def get_state_lower(levels, value):
|
||||||
warn, crit = levels
|
warn, crit = levels
|
||||||
if value < crit:
|
if value < crit:
|
||||||
return State.CRIT
|
return State.CRIT
|
||||||
@ -22,22 +36,24 @@ def getStateLower(levels, value):
|
|||||||
return State.OK
|
return State.OK
|
||||||
|
|
||||||
|
|
||||||
def discover_nextcloud_info(section):
|
def discover_nextcloud_info(section) -> DiscoveryResult:
|
||||||
yield(Service())
|
yield Service()
|
||||||
|
|
||||||
|
|
||||||
def check_nextcloud_info(params, section):
|
def check_nextcloud_info(params, section) -> CheckResult:
|
||||||
for key in section:
|
for key in section:
|
||||||
if key == "nextcloud":
|
if key == "nextcloud":
|
||||||
levels_free_space = params["levels_free_space"]
|
_level_type, levels_free_space = params["levels_free_space"]
|
||||||
levels_number_of_files = params["levels_number_of_files"]
|
_level_type, levels_number_of_files = params["levels_number_of_files"]
|
||||||
# update infos are available only from Nextcloud version 28 onwards
|
# update infos are available only from Nextcloud version 28 onwards
|
||||||
try:
|
try:
|
||||||
last_update = section[key]["last_update"]
|
last_update = section[key]["last_update"]
|
||||||
update_available = section[key]["update_available"]
|
update_available = section[key]["update_available"]
|
||||||
last_update_human = datetime.fromtimestamp(last_update)
|
last_update_human = datetime.fromtimestamp(last_update)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
last_update = "Update information not available, update to at least version 28"
|
last_update = (
|
||||||
|
"Update information not available, update to at least version 28"
|
||||||
|
)
|
||||||
update_available = "False"
|
update_available = "False"
|
||||||
last_update_human = "Information not available"
|
last_update_human = "Information not available"
|
||||||
status = section[key]["status"]
|
status = section[key]["status"]
|
||||||
@ -46,14 +62,14 @@ def check_nextcloud_info(params, section):
|
|||||||
php_version = section[key]["php_version"]
|
php_version = section[key]["php_version"]
|
||||||
webserver = section[key]["webserver"]
|
webserver = section[key]["webserver"]
|
||||||
num_files = section[key]["number_files"]
|
num_files = section[key]["number_files"]
|
||||||
num_shares = section[key]["number_shares"]
|
num_shares = section[key]["number_shares"]
|
||||||
|
|
||||||
# create graph for number of files and shares
|
# create graph for number of files and shares
|
||||||
yield(Metric("nc_num_files", num_files))
|
yield Metric("nc_num_files", num_files)
|
||||||
yield(Metric("nc_num_shares", num_shares))
|
yield Metric("nc_num_shares", num_shares)
|
||||||
|
|
||||||
# create overall result
|
# create overall result
|
||||||
summary = f"Status is {status}, Last update: {last_update_human}"
|
summary = f"Status is {status}, Last update check: {last_update_human}"
|
||||||
details = f"Nextcloud version: {version}\nPHP version: {php_version}\nWebserver: {webserver}\n\nNumber of files: {num_files}\nNumber of shares: {num_shares}\n\nFree space on disk: {render.bytes(free_space)}\n"
|
details = f"Nextcloud version: {version}\nPHP version: {php_version}\nWebserver: {webserver}\n\nNumber of files: {num_files}\nNumber of shares: {num_shares}\n\nFree space on disk: {render.bytes(free_space)}\n"
|
||||||
if status == "ok":
|
if status == "ok":
|
||||||
state = State.OK
|
state = State.OK
|
||||||
@ -64,31 +80,31 @@ def check_nextcloud_info(params, section):
|
|||||||
# Create result for available updates
|
# Create result for available updates
|
||||||
if update_available != "False":
|
if update_available != "False":
|
||||||
state = State.WARN
|
state = State.WARN
|
||||||
notice = f"Update is available"
|
notice = "Update is available"
|
||||||
else:
|
else:
|
||||||
state = State.OK
|
state = State.OK
|
||||||
notice = "No update available"
|
notice = "No update available"
|
||||||
if state != State.OK:
|
if state != State.OK:
|
||||||
yield(Result(state=state, notice=notice))
|
yield Result(state=state, notice=notice)
|
||||||
|
|
||||||
# Create result for free space on disk
|
# Create result for free space on disk
|
||||||
# Levels for free space are given in GBytes, we have to adjust this here
|
# Levels for free space are given in GBytes, we have to adjust this here
|
||||||
warn, crit = levels_free_space
|
warn, crit = levels_free_space
|
||||||
warn = warn*1024*1024*1024
|
warn = warn * 1024 * 1024 * 1024
|
||||||
crit = crit*1024*1024*1024
|
crit = crit * 1024 * 1024 * 1024
|
||||||
state = getStateLower((warn, crit), free_space)
|
state = get_state_lower((warn, crit), free_space)
|
||||||
# create graph for free space on disk
|
# create graph for free space on disk
|
||||||
yield(Metric("nc_free_space", free_space, levels=(warn,crit)))
|
yield Metric("nc_free_space", free_space, levels=(warn, crit))
|
||||||
notice = f"Remaining free space on disk: {render.bytes(free_space)}"
|
notice = f"Remaining free space on disk: {render.bytes(free_space)}"
|
||||||
if state != State.OK:
|
if state != State.OK:
|
||||||
yield(Result(state=state, notice=notice))
|
yield Result(state=state, notice=notice)
|
||||||
|
|
||||||
# Create result for number of files
|
# Create result for number of files
|
||||||
warn, crit = levels_number_of_files
|
warn, crit = levels_number_of_files
|
||||||
state = getStateUpper((warn, crit), num_files)
|
state = get_state_upper((warn, crit), num_files)
|
||||||
notice = f"Number of files: {num_files}"
|
notice = f"Number of files: {num_files}"
|
||||||
if state != State.OK:
|
if state != State.OK:
|
||||||
yield(Result(state=state, notice=notice))
|
yield Result(state=state, notice=notice)
|
||||||
elif key == "users":
|
elif key == "users":
|
||||||
num_users = section[key]["number"]
|
num_users = section[key]["number"]
|
||||||
num_active_last1hour = section[key]["active_last1hour"]
|
num_active_last1hour = section[key]["active_last1hour"]
|
||||||
@ -100,7 +116,7 @@ def check_nextcloud_info(params, section):
|
|||||||
yield Metric("nc_active_users_last_1day", num_active_last1day)
|
yield Metric("nc_active_users_last_1day", num_active_last1day)
|
||||||
yield Metric("nc_active_users_last_5min", num_active_last5min)
|
yield Metric("nc_active_users_last_5min", num_active_last5min)
|
||||||
notice = f"Number of users: {num_users}\n\nActive users last 5 min: {num_active_last5min}\nActive user since last hour: {num_active_last1hour}\nActive users since last day: {num_active_last1day}"
|
notice = f"Number of users: {num_users}\n\nActive users last 5 min: {num_active_last5min}\nActive user since last hour: {num_active_last1hour}\nActive users since last day: {num_active_last1day}"
|
||||||
yield(Result(state=State.OK, notice=notice))
|
yield Result(state=State.OK, notice=notice)
|
||||||
elif key == "storages":
|
elif key == "storages":
|
||||||
num_storages = section[key]["number"]
|
num_storages = section[key]["number"]
|
||||||
num_storages_home = section[key]["number_home"]
|
num_storages_home = section[key]["number_home"]
|
||||||
@ -112,7 +128,7 @@ def check_nextcloud_info(params, section):
|
|||||||
yield Metric("nc_num_storages_local", num_storages_local)
|
yield Metric("nc_num_storages_local", num_storages_local)
|
||||||
yield Metric("nc_num_storages_other", num_storages_other)
|
yield Metric("nc_num_storages_other", num_storages_other)
|
||||||
notice = f"Number of storages: {num_storages}\nNumber of home/local/other storages: {num_storages_home}/{num_storages_local}/{num_storages_other}"
|
notice = f"Number of storages: {num_storages}\nNumber of home/local/other storages: {num_storages_home}/{num_storages_local}/{num_storages_other}"
|
||||||
yield(Result(state=State.OK, notice=notice))
|
yield Result(state=State.OK, notice=notice)
|
||||||
elif key == "apps":
|
elif key == "apps":
|
||||||
# Workaround for Nextcloud 28, "apps" info is not always available
|
# Workaround for Nextcloud 28, "apps" info is not always available
|
||||||
try:
|
try:
|
||||||
@ -123,25 +139,30 @@ def check_nextcloud_info(params, section):
|
|||||||
else:
|
else:
|
||||||
app_versions = ""
|
app_versions = ""
|
||||||
# create graphs for number of apps
|
# create graphs for number of apps
|
||||||
levels = params["levels_apps_with_updates_available"]
|
_level_type, levels = params["levels_apps_with_updates_available"]
|
||||||
yield Metric("nc_num_apps_installed", num_apps_installed)
|
yield Metric("nc_num_apps_installed", num_apps_installed)
|
||||||
yield Metric("nc_apps_with_updates_available", num_apps_with_updates_available, levels=levels)
|
yield Metric(
|
||||||
state = getStateUpper(levels, num_apps_with_updates_available)
|
"nc_apps_with_updates_available",
|
||||||
if (app_versions == ""):
|
num_apps_with_updates_available,
|
||||||
|
levels=levels,
|
||||||
|
)
|
||||||
|
state = get_state_upper(levels, num_apps_with_updates_available)
|
||||||
|
if app_versions == "":
|
||||||
notice = f"Number of installed apps: {num_apps_installed}\nNumber of apps with updates available: {num_apps_with_updates_available}"
|
notice = f"Number of installed apps: {num_apps_installed}\nNumber of apps with updates available: {num_apps_with_updates_available}"
|
||||||
else:
|
else:
|
||||||
notice = f"Number of installed apps: {num_apps_installed}\nNumber of apps with updates available: {num_apps_with_updates_available}\nNew app versions available: {app_versions}"
|
notice = f"Number of installed apps: {num_apps_installed}\nNumber of apps with updates available: {num_apps_with_updates_available}\nNew app versions available: {app_versions}"
|
||||||
yield(Result(state=state, notice=notice))
|
yield Result(state=state, notice=notice)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
# TBD
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def parse_nextcloud_info_section(string_table):
|
def parse_nextcloud_info(string_table: StringTable) -> dict:
|
||||||
parsed_data = {
|
parsed_data = {
|
||||||
"nextcloud" : {},
|
"nextcloud": {},
|
||||||
"storages" : {},
|
"storages": {},
|
||||||
"apps" : {},
|
"apps": {},
|
||||||
"users" : {},
|
"users": {},
|
||||||
}
|
}
|
||||||
params_list = [
|
params_list = [
|
||||||
"NC_Version",
|
"NC_Version",
|
||||||
@ -163,7 +184,7 @@ def parse_nextcloud_info_section(string_table):
|
|||||||
"NC_Apps_With_Updates_Available",
|
"NC_Apps_With_Updates_Available",
|
||||||
"NC_Active_Users_Last_5Min",
|
"NC_Active_Users_Last_5Min",
|
||||||
"NC_Active_Users_Last_1Hour",
|
"NC_Active_Users_Last_1Hour",
|
||||||
"NC_Active_Users_Last_1Day"
|
"NC_Active_Users_Last_1Day",
|
||||||
]
|
]
|
||||||
for line in string_table:
|
for line in string_table:
|
||||||
if line[0] in params_list:
|
if line[0] in params_list:
|
||||||
@ -209,25 +230,23 @@ def parse_nextcloud_info_section(string_table):
|
|||||||
parsed_data["users"]["active_last1hour"] = int(value)
|
parsed_data["users"]["active_last1hour"] = int(value)
|
||||||
elif param == "NC_Active_Users_Last_1Day":
|
elif param == "NC_Active_Users_Last_1Day":
|
||||||
parsed_data["users"]["active_last1day"] = int(value)
|
parsed_data["users"]["active_last1day"] = int(value)
|
||||||
#pprint(parsed_data)
|
|
||||||
return parsed_data
|
return parsed_data
|
||||||
|
|
||||||
|
|
||||||
register.agent_section(
|
agent_section_nextcloud_info = AgentSection(
|
||||||
name="nextcloud_info",
|
name="nextcloud_info",
|
||||||
parse_function=parse_nextcloud_info_section,
|
parse_function=parse_nextcloud_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
check_plugin_nextcloud_info = CheckPlugin(
|
||||||
register.check_plugin(
|
|
||||||
name="nextcloud_info",
|
name="nextcloud_info",
|
||||||
service_name="Nextcloud Info",
|
service_name="Nextcloud Info",
|
||||||
discovery_function=discover_nextcloud_info,
|
discovery_function=discover_nextcloud_info,
|
||||||
check_function=check_nextcloud_info,
|
check_function=check_nextcloud_info,
|
||||||
check_default_parameters={
|
check_default_parameters={
|
||||||
"levels_apps_with_updates_available": (1, 2),
|
"levels_apps_with_updates_available": ("fixed", (1, 2)),
|
||||||
"levels_free_space": (8.0, 4.0),
|
"levels_free_space": ("fixed", (8.0, 4.0)),
|
||||||
"levels_number_of_files": (100000, 250000),
|
"levels_number_of_files": ("fixed", (100_000, 250_000)),
|
||||||
},
|
},
|
||||||
check_ruleset_name="nextcloud_info",
|
check_ruleset_name="nextcloud_info",
|
||||||
)
|
)
|
@ -1,9 +1,24 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from pprint import pprint
|
# pylint: disable=missing-module-docstring, unused-argument, missing-function-docstring
|
||||||
from time import time
|
# pylint: disable=line-too-long, too-many-locals
|
||||||
from .agent_based_api.v1 import register, render, Service, Result, State, Metric
|
|
||||||
|
|
||||||
def getStateUpper(levels, value):
|
from time import time
|
||||||
|
|
||||||
|
from cmk.agent_based.v2 import (
|
||||||
|
AgentSection,
|
||||||
|
CheckPlugin,
|
||||||
|
CheckResult,
|
||||||
|
Metric,
|
||||||
|
render,
|
||||||
|
Result,
|
||||||
|
Service,
|
||||||
|
State,
|
||||||
|
StringTable,
|
||||||
|
DiscoveryResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_state_upper(levels, value):
|
||||||
warn, crit = levels
|
warn, crit = levels
|
||||||
if value >= crit:
|
if value >= crit:
|
||||||
return State.CRIT
|
return State.CRIT
|
||||||
@ -11,7 +26,8 @@ def getStateUpper(levels, value):
|
|||||||
return State.WARN
|
return State.WARN
|
||||||
return State.OK
|
return State.OK
|
||||||
|
|
||||||
def getStateLower(levels, value):
|
|
||||||
|
def get_state_lower(levels, value):
|
||||||
warn, crit = levels
|
warn, crit = levels
|
||||||
if value < crit:
|
if value < crit:
|
||||||
return State.CRIT
|
return State.CRIT
|
||||||
@ -19,11 +35,13 @@ def getStateLower(levels, value):
|
|||||||
return State.WARN
|
return State.WARN
|
||||||
return State.OK
|
return State.OK
|
||||||
|
|
||||||
def discover_nextcloud_users(section):
|
|
||||||
for key in section:
|
|
||||||
yield(Service(item = key))
|
|
||||||
|
|
||||||
def check_nextcloud_users(item, params, section):
|
def discover_nextcloud_users(section) -> DiscoveryResult:
|
||||||
|
for key in section:
|
||||||
|
yield Service(item=key)
|
||||||
|
|
||||||
|
|
||||||
|
def check_nextcloud_users(item, params, section) -> CheckResult:
|
||||||
userid = item
|
userid = item
|
||||||
quota_used_percent = section[item][0]
|
quota_used_percent = section[item][0]
|
||||||
quota_used_bytes = section[item][1]
|
quota_used_bytes = section[item][1]
|
||||||
@ -32,30 +50,33 @@ def check_nextcloud_users(item, params, section):
|
|||||||
last_login_human = section[item][4]
|
last_login_human = section[item][4]
|
||||||
last_login_since = section[item][5]
|
last_login_since = section[item][5]
|
||||||
free_space = quota_total_bytes - quota_used_bytes
|
free_space = quota_total_bytes - quota_used_bytes
|
||||||
#print(free_space)
|
# print(free_space)
|
||||||
levels_quota_used = params["levels_users_quota_used"]
|
_level_type, levels_quota_used = params["levels_users_quota_used"]
|
||||||
levels_free_space = params["levels_users_free_space"]
|
_level_type, levels_free_space = params["levels_users_free_space"]
|
||||||
if last_login_human == "never":
|
if last_login_human == "never":
|
||||||
quota_used_percent = 0
|
quota_used_percent = 0
|
||||||
details = f"User ID is '{userid}', Last login: {last_login_human}"
|
details = f"User ID is '{userid}', Last login: {last_login_human}"
|
||||||
summary = f"Used quota of '{display_name}' can't be calculated yet (never logged in)"
|
summary = (
|
||||||
|
f"Used quota of '{display_name}' can't be calculated yet (never logged in)"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Levels are given in MBytes, we have to adjust this here
|
# Levels are given in MBytes, we have to adjust this here
|
||||||
warn, crit = levels_free_space
|
warn, crit = levels_free_space
|
||||||
warn = warn*1024*1024
|
warn = warn * 1024 * 1024
|
||||||
crit = crit*1024*1024
|
crit = crit * 1024 * 1024
|
||||||
state = getStateLower((warn, crit), free_space)
|
state = get_state_lower((warn, crit), free_space)
|
||||||
details = f"User ID is '{userid}'\nLast login: {last_login_human} ({last_login_since} ago)\nFree space: {render.bytes(free_space)}"
|
details = f"User ID is '{userid}'\nLast login: {last_login_human} ({last_login_since} ago)\nFree space: {render.bytes(free_space)}"
|
||||||
summary = f"Used quota of '{display_name}' is {render.percent(quota_used_percent)}, {render.bytes(quota_used_bytes)}/{render.bytes(quota_total_bytes)} used"
|
summary = f"Used quota of '{display_name}' is {render.percent(quota_used_percent)}, {render.bytes(quota_used_bytes)}/{render.bytes(quota_total_bytes)} used"
|
||||||
notice = f"Remaining free space: {render.bytes(free_space)}"
|
notice = f"Remaining free space: {render.bytes(free_space)}"
|
||||||
yield Metric("nc_users_free_space", free_space, levels=(warn, crit))
|
yield Metric("nc_users_free_space", free_space, levels=(warn, crit))
|
||||||
if state != State.OK:
|
if state != State.OK:
|
||||||
yield(Result(state=state, notice=notice))
|
yield Result(state=state, notice=notice)
|
||||||
yield Metric("nc_users_quota_used", quota_used_percent, levels=levels_quota_used)
|
yield Metric("nc_users_quota_used", quota_used_percent, levels=levels_quota_used)
|
||||||
state = getStateUpper(levels_quota_used, quota_used_percent)
|
state = get_state_upper(levels_quota_used, quota_used_percent)
|
||||||
yield(Result(state=state, summary=summary, details=details))
|
yield Result(state=state, summary=summary, details=details)
|
||||||
|
|
||||||
def parse_nextcloud_users_section(string_table):
|
|
||||||
|
def parse_nextcloud_users(string_table: StringTable) -> dict:
|
||||||
# Raw output from check:
|
# Raw output from check:
|
||||||
# userid;displayname;lastLogin;quota_free;quota_quota;quota_relative;quota_total;quota_used
|
# userid;displayname;lastLogin;quota_free;quota_quota;quota_relative;quota_total;quota_used
|
||||||
# str;str;int(milli seconds since epoch);int(bytes);int(bytes);float(percent);int(bytes);int(bytes)
|
# str;str;int(milli seconds since epoch);int(bytes);int(bytes);float(percent);int(bytes);int(bytes)
|
||||||
@ -63,7 +84,7 @@ def parse_nextcloud_users_section(string_table):
|
|||||||
for line in string_table:
|
for line in string_table:
|
||||||
userid = line[0]
|
userid = line[0]
|
||||||
display_name = line[1]
|
display_name = line[1]
|
||||||
last_login = int(line[2])/1000
|
last_login = int(line[2]) / 1000
|
||||||
if last_login == 0:
|
if last_login == 0:
|
||||||
# user never logged in
|
# user never logged in
|
||||||
last_login_human = "never"
|
last_login_human = "never"
|
||||||
@ -74,25 +95,37 @@ def parse_nextcloud_users_section(string_table):
|
|||||||
login_diff = curr_time - last_login
|
login_diff = curr_time - last_login
|
||||||
last_login_human = render.datetime(last_login)
|
last_login_human = render.datetime(last_login)
|
||||||
last_login_since = render.timespan(login_diff)
|
last_login_since = render.timespan(login_diff)
|
||||||
|
quota_quota = int(line[4])
|
||||||
|
if quota_quota == -3:
|
||||||
|
# TBD, no quota set for user
|
||||||
|
pass
|
||||||
quota_relative = float(line[5])
|
quota_relative = float(line[5])
|
||||||
quota_total = float(line[6])
|
quota_total = float(line[6])
|
||||||
quota_used = float(line[7])
|
quota_used = float(line[7])
|
||||||
parsed_data[f"{userid}"] = [quota_relative, quota_used, quota_total, display_name, last_login_human, last_login_since]
|
parsed_data[f"{userid}"] = [
|
||||||
|
quota_relative,
|
||||||
|
quota_used,
|
||||||
|
quota_total,
|
||||||
|
display_name,
|
||||||
|
last_login_human,
|
||||||
|
last_login_since,
|
||||||
|
]
|
||||||
return parsed_data
|
return parsed_data
|
||||||
|
|
||||||
register.agent_section(
|
|
||||||
name = "nextcloud_users",
|
agent_section_nextcloud_users = AgentSection(
|
||||||
parse_function = parse_nextcloud_users_section,
|
name="nextcloud_users",
|
||||||
|
parse_function=parse_nextcloud_users,
|
||||||
)
|
)
|
||||||
|
|
||||||
register.check_plugin(
|
check_plugin_nextcloud_users = CheckPlugin(
|
||||||
name = "nextcloud_users",
|
name="nextcloud_users",
|
||||||
service_name = "Nextcloud User %s",
|
service_name="Nextcloud User %s",
|
||||||
discovery_function = discover_nextcloud_users,
|
discovery_function=discover_nextcloud_users,
|
||||||
check_function = check_nextcloud_users,
|
check_function=check_nextcloud_users,
|
||||||
check_default_parameters = {
|
check_default_parameters={
|
||||||
"levels_users_quota_used": (65.0, 85.00),
|
"levels_users_quota_used": ("fixed", (65.0, 85.0)),
|
||||||
"levels_users_free_space": (256.0, 128.0)
|
"levels_users_free_space": ("fixed", (256.0, 128.0)),
|
||||||
},
|
},
|
||||||
check_ruleset_name="nextcloud_users",
|
check_ruleset_name="nextcloud_users",
|
||||||
)
|
)
|
16
nextcloud/checkman/nextcloud_database
Normal file
16
nextcloud/checkman/nextcloud_database
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
title: Nextcloud: Database information
|
||||||
|
agents: linux
|
||||||
|
catalog: unsorted
|
||||||
|
license: GPL
|
||||||
|
distribution: check_mk
|
||||||
|
description:
|
||||||
|
Works with Nextcloud version 25/26/27/28/29/30 (use at your own risk with lower versions).
|
||||||
|
Tested only with mariab as underlying database.
|
||||||
|
You have to use a username/app password combination to get access to the Nextcloud API.
|
||||||
|
You can create this app password within the personal settings of an administrative user in Nextcloud.
|
||||||
|
Got to security settings and create a new app password.
|
||||||
|
The user must not be secured with 2FA.
|
||||||
|
Shows several information about a Nextcloud database (type/version/size).
|
||||||
|
The check will raise WARN/CRIT if Database PHP OPcache hit rate is below the configurable levels.
|
||||||
|
inventory:
|
||||||
|
one service is created (with several details)
|
@ -4,15 +4,15 @@ catalog: unsorted
|
|||||||
license: GPL
|
license: GPL
|
||||||
distribution: check_mk
|
distribution: check_mk
|
||||||
description:
|
description:
|
||||||
Works with Nextcloud version 25/26/27 (use at your own risk with lower versions).
|
Works with Nextcloud version 25/26/27/28/29/30 (use at your own risk with lower versions).
|
||||||
Tested only with mysql/mariab as underlying database.
|
Tested only with mariab as underlying database.
|
||||||
You may use a username/password or username/token combination to get access to the Nextcloud API.
|
You have to use a username/app password combination to get access to the Nextcloud API.
|
||||||
You can create this token within the personal settings of an administrative user in Nextcloud.
|
You can create this app password within the personal settings of an administrative user in Nextcloud.
|
||||||
Got to security settings and create a new app password, use this for the token.
|
Got to security settings and create a new app password.
|
||||||
The user must not be secured with 2FA.
|
The user must not be secured with 2FA.
|
||||||
Shows several information about a Nextcloud instance, e.g. number of files/storages/(active)users, free space on disk.
|
Shows several information about a Nextcloud instance, e.g. number of files/storages/(active)users, free space on disk.
|
||||||
The check will raise CRIT if the Nextcloud instance is not in "ok" state.
|
The check will raise CRIT if the Nextcloud instance is not in "ok" state.
|
||||||
The check will raise WARN if there is an update availbale for Nextcloud.
|
The check will raise WARN if there is an update available for Nextcloud.
|
||||||
The check will raise WARN/CRIT if free space on disk is below the configurable levels.
|
The check will raise WARN/CRIT if free space on disk is below the configurable levels.
|
||||||
The check will raise WARN/CRIT if the number of installed apps with available updates is above the configurable levels.
|
The check will raise WARN/CRIT if the number of installed apps with available updates is above the configurable levels.
|
||||||
The check will raise WARN/CRIT if the number of files is above the configurable levels.
|
The check will raise WARN/CRIT if the number of files is above the configurable levels.
|
@ -4,10 +4,11 @@ catalog: unsorted
|
|||||||
license: GPL
|
license: GPL
|
||||||
distribution: check_mk
|
distribution: check_mk
|
||||||
description:
|
description:
|
||||||
Works with Nextcloud version 25/26/27 (use at your own risk with lower versions).
|
Works with Nextcloud version 25/26/27/28/29/30 (use at your own risk with lower versions).
|
||||||
You may use a username/password or username/token combination to get access to the Nextcloud API.
|
Tested only with mariab as underlying database.
|
||||||
You can create this token within the personal settings of an administrative user in Nextcloud.
|
You have to use a username/app password combination to get access to the Nextcloud API.
|
||||||
Got to security settings and create a new app password, use this for the token.
|
You can create this app password within the personal settings of an administrative user in Nextcloud.
|
||||||
|
Got to security settings and create a new app password.
|
||||||
The user must not be secured with 2FA.
|
The user must not be secured with 2FA.
|
||||||
Shows the usage of storage quota used by each user in percent of the maximum allowed quota.
|
Shows the usage of storage quota used by each user in percent of the maximum allowed quota.
|
||||||
Shows the remaining free space for each user based on his/her max quota and used space.
|
Shows the remaining free space for each user based on his/her max quota and used space.
|
198
nextcloud/graphing/graph_nextcloud.py
Normal file
198
nextcloud/graphing/graph_nextcloud.py
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""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, SINotation, Metric, Unit
|
||||||
|
from cmk.graphing.v1.perfometers import Open, Closed, FocusRange, Perfometer, Stacked
|
||||||
|
|
||||||
|
|
||||||
|
metric_nc_num_users = Metric(
|
||||||
|
name="nc_num_users",
|
||||||
|
title=Title("Number of Users"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.PURPLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_num_shares = Metric(
|
||||||
|
name="nc_num_shares",
|
||||||
|
title=Title("Number of Shares"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.BROWN,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_num_storages = Metric(
|
||||||
|
name="nc_num_storages",
|
||||||
|
title=Title("Number of Storages"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.BLUE,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_num_storages_home = Metric(
|
||||||
|
name="nc_num_storages_home",
|
||||||
|
title=Title("Number of Home Storages"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.GREEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_num_storages_local = Metric(
|
||||||
|
name="nc_num_storages_local",
|
||||||
|
title=Title("Number of Local Storages"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.DARK_GREEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
metric_nc_num_storages_other = Metric(
|
||||||
|
name="nc_num_storages_other",
|
||||||
|
title=Title("Number of Other Storages"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.LIGHT_GREEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_num_files = Metric(
|
||||||
|
name="nc_num_files",
|
||||||
|
title=Title("Number of Files"),
|
||||||
|
unit=Unit(SINotation("")),
|
||||||
|
color=Color.RED,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_num_apps_installed = Metric(
|
||||||
|
name="nc_num_apps_installed",
|
||||||
|
title=Title("Number of installed Apps"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.GREEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_apps_with_updates_available = Metric(
|
||||||
|
name="nc_apps_with_updates_available",
|
||||||
|
title=Title("Number of installed Apps with Updates Available"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.RED,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_active_users_last_5min = Metric(
|
||||||
|
name="nc_active_users_last_5min",
|
||||||
|
title=Title("Number of Active Users in the last 5 minutes"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.LIGHT_RED,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_active_users_last_1hour = Metric(
|
||||||
|
name="nc_active_users_last_1hour",
|
||||||
|
title=Title("Number of Active Users in the last 1 hour"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.LIGHT_GREEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_active_users_last_1day = Metric(
|
||||||
|
name="nc_active_users_last_1day",
|
||||||
|
title=Title("Number of Active Users in the last 1 day"),
|
||||||
|
unit=Unit(DecimalNotation("")),
|
||||||
|
color=Color.LIGHT_BLUE,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_users_free_space = Metric(
|
||||||
|
name="nc_users_free_space",
|
||||||
|
title=Title("Free Space of User"),
|
||||||
|
unit=Unit(SINotation("bytes")),
|
||||||
|
color=Color.LIGHT_BLUE,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_free_space = Metric(
|
||||||
|
name="nc_free_space",
|
||||||
|
title=Title("Free Space on Disk"),
|
||||||
|
unit=Unit(SINotation("bytes")),
|
||||||
|
color=Color.DARK_BLUE,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_database_size = Metric(
|
||||||
|
name="nc_database_size",
|
||||||
|
title=Title("Database Size"),
|
||||||
|
unit=Unit(SINotation("bytes")),
|
||||||
|
color=Color.GREEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
metric_nc_database_opcache_hit_rate = Metric(
|
||||||
|
name="nc_database_opcache_hit_rate",
|
||||||
|
title=Title("Database PHP OPCache Hit Rate"),
|
||||||
|
unit=Unit(DecimalNotation("%")),
|
||||||
|
color=Color.LIGHT_BLUE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
metric_nc_users_quota_used = Metric(
|
||||||
|
name="nc_users_quota_used",
|
||||||
|
title=Title("Quota used"),
|
||||||
|
unit=Unit(DecimalNotation("%")),
|
||||||
|
color=Color.BLUE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
graph_nc_number_of_users_shares_storages = Graph(
|
||||||
|
name="number_of_users_shares_storages_combined",
|
||||||
|
title=Title("Number of Users, Shares and Storages"),
|
||||||
|
# names here refer to the metric names above
|
||||||
|
simple_lines=["nc_num_users", "nc_num_shares", "nc_num_storages"],
|
||||||
|
minimal_range=MinimalRange(0, 200),
|
||||||
|
)
|
||||||
|
|
||||||
|
graph_nc_number_of_storage_types = Graph(
|
||||||
|
name="number_of_storage_types_combined",
|
||||||
|
title=Title("Number of Storage Types"),
|
||||||
|
# names here refer to the metric names above
|
||||||
|
simple_lines=[
|
||||||
|
"nc_num_storages_home",
|
||||||
|
"nc_num_storages_local",
|
||||||
|
"nc_num_storages_other",
|
||||||
|
],
|
||||||
|
minimal_range=MinimalRange(0, 100),
|
||||||
|
)
|
||||||
|
|
||||||
|
graph_nc_number_of_active_users = Graph(
|
||||||
|
name="number_of_active_users_combined",
|
||||||
|
title=Title("Number of Active Users"),
|
||||||
|
# names here refer to the metric names above
|
||||||
|
simple_lines=[
|
||||||
|
"nc_active_users_last_5min",
|
||||||
|
"nc_active_users_last_1hour",
|
||||||
|
"nc_active_users_last_1day",
|
||||||
|
],
|
||||||
|
minimal_range=MinimalRange(0, 100),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
perfometer_nc_database_op_cache_hit_rate = Perfometer(
|
||||||
|
name="nc_database_opcache_hit_rate",
|
||||||
|
focus_range=FocusRange(
|
||||||
|
Closed(0),
|
||||||
|
Closed(100),
|
||||||
|
),
|
||||||
|
segments=("nc_database_opcache_hit_rate",),
|
||||||
|
)
|
||||||
|
|
||||||
|
perfometer_nc_database_size = Perfometer(
|
||||||
|
name="nc_database_size",
|
||||||
|
focus_range=FocusRange(Open(0.0), Open(500.0)),
|
||||||
|
segments=["nc_database_size"],
|
||||||
|
)
|
||||||
|
|
||||||
|
perfometer_nc_quota_used = Perfometer(
|
||||||
|
name="nc_users_quota_used",
|
||||||
|
focus_range=FocusRange(Closed(0), Closed(100)),
|
||||||
|
segments=["nc_users_quota_used"],
|
||||||
|
)
|
||||||
|
|
||||||
|
perfometer_nc_info = Stacked(
|
||||||
|
name="nc_info",
|
||||||
|
lower=Perfometer(
|
||||||
|
name="nc_num_users",
|
||||||
|
focus_range=FocusRange(Closed(0), Open(200)),
|
||||||
|
segments=["nc_num_users"],
|
||||||
|
),
|
||||||
|
upper=Perfometer(
|
||||||
|
name="nc_num_apps_installed",
|
||||||
|
focus_range=FocusRange(Closed(0), Open(100)),
|
||||||
|
segments=["nc_num_apps_installed"],
|
||||||
|
),
|
||||||
|
)
|
312
nextcloud/libexec/agent_nextcloud
Executable file
312
nextcloud/libexec/agent_nextcloud
Executable file
@ -0,0 +1,312 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# pylint: disable=missing-module-docstring, missing-class-docstring, missing-function-docstring
|
||||||
|
# pylint: disable=too-many-branches, line-too-long, too-many-statements
|
||||||
|
# pylint: disable=too-many-arguments, too-many-positional-arguments, too-many-locals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
import urllib3
|
||||||
|
from requests.structures import CaseInsensitiveDict
|
||||||
|
|
||||||
|
NC_API_ENDPOINT = "ocs/v2.php/apps/serverinfo/api/v1/info?format=json"
|
||||||
|
NC_API_ENDPOINT_ALL_USERS = "ocs/v1.php/cloud/users?format=json"
|
||||||
|
NC_API_ENDPOINT_USER = "ocs/v1.php/cloud/users"
|
||||||
|
|
||||||
|
|
||||||
|
def get_args() -> argparse.Namespace:
|
||||||
|
parser: argparse.ArgumentParser = argparse.ArgumentParser(
|
||||||
|
description="Nextcloud server parameters"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--hostname", required=True, type=str, help="Hostname or IP of nextcloud server"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--username", required=True, type=str, help="User with admin privileges"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--password", required=True, type=str, help="App password for the user"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--port",
|
||||||
|
required=False,
|
||||||
|
type=int,
|
||||||
|
help="Port where the server is listening on (if not HTTP/HTTPS)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--folder",
|
||||||
|
required=False,
|
||||||
|
type=str,
|
||||||
|
help="Folder if not installed in web root directory",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-https",
|
||||||
|
required=False,
|
||||||
|
default=False,
|
||||||
|
help="Disable HTTPS",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-cert-check",
|
||||||
|
required=False,
|
||||||
|
default=False,
|
||||||
|
help="Disable certificate checks",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def create_url(endpoint, hostname, protocol, port, folder):
|
||||||
|
# these parameters are needed, otherwise no information about updates regarding apps and Nextcloud itself are reported (since version 28)
|
||||||
|
params = "skipApps=false&skipUpdate=false"
|
||||||
|
if folder == "":
|
||||||
|
url = f"{protocol}://{hostname}:{port}/{endpoint}"
|
||||||
|
else:
|
||||||
|
url = f"{protocol}://{hostname}:{port}/{folder}/{endpoint}"
|
||||||
|
if endpoint == NC_API_ENDPOINT:
|
||||||
|
url = f"{url}&{params}"
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def create_url_user(user, endpoint, hostname, protocol, port, folder):
|
||||||
|
params = "format=json"
|
||||||
|
if folder == "":
|
||||||
|
url = f"{protocol}://{hostname}:{port}/{endpoint}/{user}?{params}"
|
||||||
|
else:
|
||||||
|
url = f"{protocol}://{hostname}:{port}/{folder}/{endpoint}/{user}?{params}"
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def get_session(username, secret):
|
||||||
|
session = requests.session()
|
||||||
|
session.cookies.set("SameSite", "Strict")
|
||||||
|
session.auth = (username, secret)
|
||||||
|
session.headers["Accept"] = "application/json"
|
||||||
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(session, url, verify):
|
||||||
|
data = {}
|
||||||
|
headers = CaseInsensitiveDict()
|
||||||
|
headers["Accept"] = "application/json"
|
||||||
|
response = session.get(url, headers=headers, verify=verify)
|
||||||
|
status = response.status_code
|
||||||
|
if status == 200:
|
||||||
|
jsdata = response.text
|
||||||
|
data = json.loads(jsdata) # returns a dictionary
|
||||||
|
return data
|
||||||
|
if status == 503:
|
||||||
|
# this code is reported when maintenance mode is on
|
||||||
|
sys.stderr.write(
|
||||||
|
f"Request response code is {response.status_code} with URL {url}, maybe maintenance mode is on?\n"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
sys.stderr.write(
|
||||||
|
f"Request response code is {response.status_code} with URL {url}\n"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_data_all_users(session, url, verify):
|
||||||
|
headers = CaseInsensitiveDict()
|
||||||
|
headers["Accept"] = "application/json"
|
||||||
|
headers["OCS-APIRequest"] = "true"
|
||||||
|
response = session.get(url, headers=headers, verify=verify)
|
||||||
|
status = response.status_code
|
||||||
|
if status == 200:
|
||||||
|
jsdata = response.text
|
||||||
|
data = json.loads(jsdata) # returns a dictionary
|
||||||
|
return data
|
||||||
|
sys.stderr.write(
|
||||||
|
f"Request response code is {response.status_code} with URL {url}\n"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_data_user(session, url, verify):
|
||||||
|
headers = CaseInsensitiveDict()
|
||||||
|
headers["Accept"] = "application/json"
|
||||||
|
headers["OCS-APIRequest"] = "true"
|
||||||
|
response = session.get(url, headers=headers, verify=verify)
|
||||||
|
status = response.status_code
|
||||||
|
if status == 200:
|
||||||
|
jsdata = response.text
|
||||||
|
data = json.loads(jsdata) # returns a dictionary
|
||||||
|
return data
|
||||||
|
|
||||||
|
sys.stderr.write(
|
||||||
|
f"Request response code is {response.status_code} with URL {url}\n"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def do_cmk_output(data):
|
||||||
|
apps_with_updates_available = {}
|
||||||
|
str_apps_with_updates_available = ""
|
||||||
|
print("<<<nextcloud_info:sep(59)>>>")
|
||||||
|
print(f"NC_Version;{data['ocs']['data']['nextcloud']['system']['version']}")
|
||||||
|
print(f"NC_Freespace;{data['ocs']['data']['nextcloud']['system']['freespace']}")
|
||||||
|
print(f"NC_Status;{data['ocs']['meta']['status']}")
|
||||||
|
# This update info is available only from version 28 onwards, so the key "update" does not exist in all versions before
|
||||||
|
try:
|
||||||
|
print(
|
||||||
|
f"NC_Last_Update;{data['ocs']['data']['nextcloud']['system']['update']['lastupdatedat']}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"NC_Update_Available;{data['ocs']['data']['nextcloud']['system']['update']['available']}"
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
# TBD
|
||||||
|
pass
|
||||||
|
print(f"NC_Num_Users;{data['ocs']['data']['nextcloud']['storage']['num_users']}")
|
||||||
|
print(f"NC_Num_Files;{data['ocs']['data']['nextcloud']['storage']['num_files']}")
|
||||||
|
print(f"NC_Num_Shares;{data['ocs']['data']['nextcloud']['shares']['num_shares']}")
|
||||||
|
print(
|
||||||
|
f"NC_Num_Storages;{data['ocs']['data']['nextcloud']['storage']['num_storages']}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"NC_Num_Storages_Home;{data['ocs']['data']['nextcloud']['storage']['num_storages_home']}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"NC_Num_Storages_Local;{data['ocs']['data']['nextcloud']['storage']['num_storages_local']}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"NC_Num_Storages_Other;{data['ocs']['data']['nextcloud']['storage']['num_storages_other']}"
|
||||||
|
)
|
||||||
|
# Workaround for Nextcloud 28.0.1 (KeyError "apps")
|
||||||
|
try:
|
||||||
|
print(
|
||||||
|
f"NC_Num_Apps_Installed;{data['ocs']['data']['nextcloud']['system']['apps']['num_installed']}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"NC_Num_Apps_Updates_Available;{data['ocs']['data']['nextcloud']['system']['apps']['num_updates_available']}"
|
||||||
|
)
|
||||||
|
apps_with_updates_available = data["ocs"]["data"]["nextcloud"]["system"][
|
||||||
|
"apps"
|
||||||
|
]["app_updates"]
|
||||||
|
if apps_with_updates_available:
|
||||||
|
for app, version in apps_with_updates_available.items():
|
||||||
|
str_apps_with_updates_available = (
|
||||||
|
str_apps_with_updates_available + app + "/" + version + " "
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"NC_Apps_With_Updates_Available;{str_apps_with_updates_available}"
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
# TBD
|
||||||
|
pass
|
||||||
|
print(
|
||||||
|
f"NC_Active_Users_Last_5Min;{data['ocs']['data']['activeUsers']['last5minutes']}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"NC_Active_Users_Last_1Hour;{data['ocs']['data']['activeUsers']['last1hour']}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"NC_Active_Users_Last_1Day;{data['ocs']['data']['activeUsers']['last24hours']}"
|
||||||
|
)
|
||||||
|
print(f"NC_Webserver;{data['ocs']['data']['server']['webserver']}")
|
||||||
|
print(f"NC_PHP_Version;{data['ocs']['data']['server']['php']['version']}")
|
||||||
|
|
||||||
|
print("<<<nextcloud_database:sep(59)>>>")
|
||||||
|
print(f"NC_Database_Type;{data['ocs']['data']['server']['database']['type']}")
|
||||||
|
print(f"NC_Database_Version;{data['ocs']['data']['server']['database']['version']}")
|
||||||
|
print(f"NC_Database_Size;{data['ocs']['data']['server']['database']['size']}")
|
||||||
|
# opcache entry does not exist if opcache_get_status is disabled by server settings
|
||||||
|
# thanks to Marcus Klein from Iteratio to report (and solve!) this bug
|
||||||
|
if data["ocs"]["data"]["server"]["php"]["opcache"]:
|
||||||
|
print(
|
||||||
|
f"NC_OPCache_Hit_Rate;{data['ocs']['data']['server']['php']['opcache']['opcache_statistics']['opcache_hit_rate']}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("NC_OPCache_Hit_Rate;0")
|
||||||
|
|
||||||
|
|
||||||
|
def do_cmk_output_all_users(session, data, verify, hostname, protocol, port, folder):
|
||||||
|
print("<<<nextcloud_users:sep(59)>>>")
|
||||||
|
for user in data["ocs"]["data"]["users"]:
|
||||||
|
nc_url = create_url_user(
|
||||||
|
user, NC_API_ENDPOINT_USER, hostname, protocol, port, folder
|
||||||
|
)
|
||||||
|
user_data = get_data_user(session, nc_url, verify)
|
||||||
|
userid = user_data["ocs"]["data"]["id"]
|
||||||
|
displayname = user_data["ocs"]["data"]["displayname"]
|
||||||
|
lastlogin = int(user_data["ocs"]["data"]["lastLogin"])
|
||||||
|
if lastlogin == 0:
|
||||||
|
# user has never logged in
|
||||||
|
quota_free = -1
|
||||||
|
quota_quota = -1
|
||||||
|
quota_relative = -1
|
||||||
|
quota_total = -1
|
||||||
|
quota_used = -1
|
||||||
|
else:
|
||||||
|
quota_free = user_data["ocs"]["data"]["quota"]["free"]
|
||||||
|
# quota_quota == -3 --> unlimited
|
||||||
|
quota_quota = user_data["ocs"]["data"]["quota"]["quota"]
|
||||||
|
# quota_relative = used * 100 / (free + used)
|
||||||
|
quota_relative = user_data["ocs"]["data"]["quota"]["relative"]
|
||||||
|
quota_total = user_data["ocs"]["data"]["quota"]["total"]
|
||||||
|
quota_used = user_data["ocs"]["data"]["quota"]["used"]
|
||||||
|
print(
|
||||||
|
f"{userid};{displayname};{lastlogin};{quota_free};{quota_quota};{quota_relative};{quota_total};{quota_used}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# get and check parameters
|
||||||
|
params: argparse.Namespace = get_args()
|
||||||
|
if params.hostname is None:
|
||||||
|
sys.stderr.write("No hostname given.\n")
|
||||||
|
sys.exit(1)
|
||||||
|
if params.username is None:
|
||||||
|
sys.stderr.write("No username given.\n")
|
||||||
|
sys.exit(1)
|
||||||
|
if params.password is None:
|
||||||
|
sys.stderr.write("No app password given.\n")
|
||||||
|
sys.exit(1)
|
||||||
|
hostname = params.hostname
|
||||||
|
username = params.username
|
||||||
|
pwd = params.password
|
||||||
|
if params.folder is None:
|
||||||
|
folder = ""
|
||||||
|
else:
|
||||||
|
folder = params.folder
|
||||||
|
if params.no_cert_check:
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
verify = False
|
||||||
|
else:
|
||||||
|
verify = True
|
||||||
|
if params.port is None:
|
||||||
|
if params.no_https:
|
||||||
|
protocol = "http"
|
||||||
|
port = "80"
|
||||||
|
else:
|
||||||
|
protocol = "https"
|
||||||
|
port = "443"
|
||||||
|
else:
|
||||||
|
if params.no_https:
|
||||||
|
protocol = "http"
|
||||||
|
else:
|
||||||
|
protocol = "https"
|
||||||
|
port = params.port
|
||||||
|
if protocol == "http" and port == "443":
|
||||||
|
sys.stderr.write("Combining HTTP with port 443 is not supported.\n")
|
||||||
|
sys.exit(1)
|
||||||
|
if protocol == "https" and port == "80":
|
||||||
|
sys.stderr.write("Combining HTTPS with port 80 is not supported.\n")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# create session
|
||||||
|
session = get_session(username, pwd)
|
||||||
|
nc_url = create_url(NC_API_ENDPOINT, hostname, protocol, port, folder)
|
||||||
|
nc_data = get_data(session, nc_url, verify)
|
||||||
|
do_cmk_output(nc_data)
|
||||||
|
nc_url = create_url(NC_API_ENDPOINT_ALL_USERS, hostname, protocol, port, folder)
|
||||||
|
nc_data = get_data_all_users(session, nc_url, verify)
|
||||||
|
do_cmk_output_all_users(session, nc_data, verify, hostname, protocol, port, folder)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
45
nextcloud/rulesets/nextcloud_database_rules.py
Normal file
45
nextcloud/rulesets/nextcloud_database_rules.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#!/user/bin/env python3
|
||||||
|
"""parameter form ruleset for Nextcloud databases"""
|
||||||
|
|
||||||
|
from cmk.rulesets.v1 import Title
|
||||||
|
from cmk.rulesets.v1.form_specs import (
|
||||||
|
DefaultValue,
|
||||||
|
DictElement,
|
||||||
|
Dictionary,
|
||||||
|
Float,
|
||||||
|
LevelDirection,
|
||||||
|
SimpleLevels,
|
||||||
|
)
|
||||||
|
from cmk.rulesets.v1.rule_specs import HostCondition, CheckParameters, Topic
|
||||||
|
|
||||||
|
|
||||||
|
# function name should begin with an underscore to limit it's visibility
|
||||||
|
def _parameter_form():
|
||||||
|
return Dictionary(
|
||||||
|
elements={
|
||||||
|
"levels_database_opcache_hit_rate": DictElement(
|
||||||
|
parameter_form=SimpleLevels(
|
||||||
|
title=Title("Levels for database PHP op cache hit rate"),
|
||||||
|
form_spec_template=Float(),
|
||||||
|
level_direction=LevelDirection.LOWER,
|
||||||
|
prefill_fixed_levels=DefaultValue(value=(95.0, 85.0)),
|
||||||
|
),
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# name must begin with "rule_spec_", should refer to the used check plugin
|
||||||
|
# must be an instance of "CheckParameters"
|
||||||
|
rule_spec_nextcloud_database = CheckParameters(
|
||||||
|
# "name" should be the same as the check plugin
|
||||||
|
name="nextcloud_database",
|
||||||
|
# the title is shown in the GUI
|
||||||
|
title=Title("Levels for database PHP op cache hit rate"),
|
||||||
|
# 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(),
|
||||||
|
)
|
66
nextcloud/rulesets/nextcloud_info_rules.py
Normal file
66
nextcloud/rulesets/nextcloud_info_rules.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#!/user/bin/env python3
|
||||||
|
"""parameter form ruleset for Nextcloud systems"""
|
||||||
|
|
||||||
|
from cmk.rulesets.v1 import Title
|
||||||
|
from cmk.rulesets.v1.form_specs import (
|
||||||
|
DefaultValue,
|
||||||
|
DictElement,
|
||||||
|
Dictionary,
|
||||||
|
Float,
|
||||||
|
LevelDirection,
|
||||||
|
SimpleLevels,
|
||||||
|
Integer,
|
||||||
|
)
|
||||||
|
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_apps_with_updates_available": DictElement(
|
||||||
|
parameter_form=SimpleLevels(
|
||||||
|
title=Title("Number of installed apps with updates available"),
|
||||||
|
form_spec_template=Integer(),
|
||||||
|
level_direction=LevelDirection.UPPER,
|
||||||
|
prefill_fixed_levels=DefaultValue(value=(1, 2)),
|
||||||
|
),
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
"levels_free_space": DictElement(
|
||||||
|
parameter_form=SimpleLevels(
|
||||||
|
title=Title("Levels for free disk space overall"),
|
||||||
|
form_spec_template=Float(unit_symbol="GBytes"),
|
||||||
|
level_direction=LevelDirection.LOWER,
|
||||||
|
prefill_fixed_levels=DefaultValue(value=(8.0, 4.0)),
|
||||||
|
),
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
"levels_number_of_files": DictElement(
|
||||||
|
parameter_form=SimpleLevels(
|
||||||
|
title=Title("Number of files overall"),
|
||||||
|
form_spec_template=Integer(),
|
||||||
|
level_direction=LevelDirection.UPPER,
|
||||||
|
prefill_fixed_levels=DefaultValue(value=(100_000, 250_000)),
|
||||||
|
),
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# name must begin with "rule_spec_", should refer to the used check plugin
|
||||||
|
# must be an instance of "CheckParameters"
|
||||||
|
rule_spec_nextcloud_info = CheckParameters(
|
||||||
|
# "name" should be the same as the check plugin
|
||||||
|
name="nextcloud_info",
|
||||||
|
# the title is shown in the GUI
|
||||||
|
title=Title("Various parameters for Nextcloud systems"),
|
||||||
|
# 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,
|
||||||
|
# define the label in the GUI where you can restrict the
|
||||||
|
# settings to one or mor specific storages (item)
|
||||||
|
condition=HostCondition(),
|
||||||
|
)
|
105
nextcloud/rulesets/nextcloud_params.py
Normal file
105
nextcloud/rulesets/nextcloud_params.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# pylint: disable=line-too-long, missing-module-docstring
|
||||||
|
|
||||||
|
from cmk.rulesets.v1 import Help, Label, Title
|
||||||
|
from cmk.rulesets.v1.form_specs import (
|
||||||
|
BooleanChoice,
|
||||||
|
DictElement,
|
||||||
|
Dictionary,
|
||||||
|
migrate_to_password,
|
||||||
|
Password,
|
||||||
|
String,
|
||||||
|
Integer,
|
||||||
|
validators,
|
||||||
|
InputHint,
|
||||||
|
)
|
||||||
|
from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic
|
||||||
|
|
||||||
|
|
||||||
|
def _form_spec_special_agent_nextcloud() -> Dictionary:
|
||||||
|
return Dictionary(
|
||||||
|
title=Title("Nextcloud Server Information"),
|
||||||
|
help_text=Help("Checking Nextcloud servers via API"),
|
||||||
|
elements={
|
||||||
|
"hostname": DictElement(
|
||||||
|
required=True,
|
||||||
|
parameter_form=String(
|
||||||
|
title=Title("Hostname"),
|
||||||
|
help_text=Help(
|
||||||
|
"Hostname of Nextcloud server (bare FQDN or IP), mandatory, eg. nextcloud.yourdomain.tld"
|
||||||
|
),
|
||||||
|
custom_validate=(validators.LengthInRange(min_value=1),),
|
||||||
|
prefill=InputHint("nextcloud.yourdomain.tld"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"username": DictElement(
|
||||||
|
required=True,
|
||||||
|
parameter_form=String(
|
||||||
|
title=Title("Username"),
|
||||||
|
help_text=Help("Username with administrative rights, mandatory"),
|
||||||
|
custom_validate=(validators.LengthInRange(min_value=1),),
|
||||||
|
prefill=InputHint("admin"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"password": DictElement(
|
||||||
|
required=True,
|
||||||
|
parameter_form=Password(
|
||||||
|
title=Title("App Password"),
|
||||||
|
help_text=Help(
|
||||||
|
"Specify app password, mandatory, use Personal Settings|Security|Devices and Sessions within the NC UI to create one for the given user"
|
||||||
|
),
|
||||||
|
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 if not listening to HTTP(S), optional"
|
||||||
|
),
|
||||||
|
prefill=InputHint(8080),
|
||||||
|
custom_validate=(validators.NetworkPort(),),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"folder": DictElement(
|
||||||
|
required=False,
|
||||||
|
parameter_form=String(
|
||||||
|
title=Title("Folder"),
|
||||||
|
help_text=Help(
|
||||||
|
"Specify subfolder if your Nextcloud instance is not installed in the web root, no trailing/leading slashes, optional"
|
||||||
|
),
|
||||||
|
prefill=InputHint("nextcloud"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"no_https": DictElement(
|
||||||
|
required=False,
|
||||||
|
parameter_form=BooleanChoice(
|
||||||
|
title=Title("Protocol handling"),
|
||||||
|
label=Label("Disable HTTPS"),
|
||||||
|
help_text=Help(
|
||||||
|
"Activate to disable encryption (not recommended), optional"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"no_cert_check": DictElement(
|
||||||
|
required=False,
|
||||||
|
parameter_form=BooleanChoice(
|
||||||
|
title=Title("Certificate handling"),
|
||||||
|
label=Label("Disable certificate validation"),
|
||||||
|
help_text=Help(
|
||||||
|
"Activate to disable certificate validation (not recommended), optional"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
rule_spec_nextcloud = SpecialAgent(
|
||||||
|
name="nextcloud",
|
||||||
|
title=Title("Nextcloud connection parameters"),
|
||||||
|
topic=Topic.APPLICATIONS,
|
||||||
|
parameter_form=_form_spec_special_agent_nextcloud,
|
||||||
|
)
|
54
nextcloud/rulesets/nextcloud_users_rules.py
Normal file
54
nextcloud/rulesets/nextcloud_users_rules.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#!/user/bin/env python3
|
||||||
|
"""parameter form ruleset for Nextcloud users"""
|
||||||
|
|
||||||
|
from cmk.rulesets.v1 import Title
|
||||||
|
from cmk.rulesets.v1.form_specs import (
|
||||||
|
DefaultValue,
|
||||||
|
DictElement,
|
||||||
|
Dictionary,
|
||||||
|
Float,
|
||||||
|
LevelDirection,
|
||||||
|
SimpleLevels,
|
||||||
|
)
|
||||||
|
from cmk.rulesets.v1.rule_specs import HostAndItemCondition, CheckParameters, Topic
|
||||||
|
|
||||||
|
|
||||||
|
# function name should begin with an underscore to limit it's visibility
|
||||||
|
def _parameter_form():
|
||||||
|
return Dictionary(
|
||||||
|
elements={
|
||||||
|
"levels_users_quota_used": DictElement(
|
||||||
|
parameter_form=SimpleLevels(
|
||||||
|
title=Title("Levels for quota usage of users"),
|
||||||
|
form_spec_template=Float(unit_symbol="%"),
|
||||||
|
level_direction=LevelDirection.UPPER,
|
||||||
|
prefill_fixed_levels=DefaultValue(value=(65.0, 85.0)),
|
||||||
|
),
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
"levels_users_free_space": DictElement(
|
||||||
|
parameter_form=SimpleLevels(
|
||||||
|
title=Title("Levels for free disk space of users"),
|
||||||
|
form_spec_template=Float(unit_symbol="MBytes"),
|
||||||
|
level_direction=LevelDirection.LOWER,
|
||||||
|
prefill_fixed_levels=DefaultValue(value=(256.0, 128.0)),
|
||||||
|
),
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# name must begin with "rule_spec_", should refer to the used check plugin
|
||||||
|
# must be an instance of "CheckParameters"
|
||||||
|
rule_spec_nextcloud_users = CheckParameters(
|
||||||
|
# "name" should be the same as the check plugin
|
||||||
|
name="nextcloud_users",
|
||||||
|
# the title is shown in the GUI
|
||||||
|
title=Title("Levels for Nextcloud users"),
|
||||||
|
# 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=HostAndItemCondition(item_title=Title("User ID")),
|
||||||
|
)
|
54
nextcloud/server_side_calls/agent_nextcloud.py
Normal file
54
nextcloud/server_side_calls/agent_nextcloud.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# pylint: disable=missing-module-docstring, missing-class-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 NextcloudParams(BaseModel):
|
||||||
|
hostname: str | None = None
|
||||||
|
username: str | None = None
|
||||||
|
password: Secret | None = None
|
||||||
|
port: int | None = None
|
||||||
|
folder: str | None = None
|
||||||
|
no_https: bool = False
|
||||||
|
no_cert_check: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
def agent_nextcloud_arguments(
|
||||||
|
params: NextcloudParams, _host_config: HostConfig
|
||||||
|
) -> Iterable[SpecialAgentCommand]:
|
||||||
|
# print(f"Params: {params}")
|
||||||
|
command_arguments: list[str | Secret] = []
|
||||||
|
if params.hostname is not None:
|
||||||
|
command_arguments += ["--hostname", params.hostname]
|
||||||
|
if params.username is not None:
|
||||||
|
command_arguments += ["--username", params.username]
|
||||||
|
if params.password is not None:
|
||||||
|
command_arguments += ["--password", params.password.unsafe()]
|
||||||
|
if params.port is not None:
|
||||||
|
command_arguments += ["--port", str(params.port)]
|
||||||
|
if params.folder is not None:
|
||||||
|
command_arguments += ["--folder", params.folder]
|
||||||
|
if params.no_https:
|
||||||
|
command_arguments.append("--no-https")
|
||||||
|
if params.no_cert_check:
|
||||||
|
command_arguments.append("--no-cert-check")
|
||||||
|
# command_arguments.append(host_config.name)
|
||||||
|
# print(f"Command Args: {command_arguments}")
|
||||||
|
yield SpecialAgentCommand(command_arguments=command_arguments)
|
||||||
|
|
||||||
|
|
||||||
|
special_agent_nextcloud = SpecialAgentConfig(
|
||||||
|
name="nextcloud",
|
||||||
|
parameter_parser=NextcloudParams.model_validate,
|
||||||
|
commands_function=agent_nextcloud_arguments,
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user