313 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			313 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/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()
 |