#!/usr/bin/env python
# -*- coding: utf-8 -*-

# check_opsi is part of the desktop management solution opsi
# (open pc server integration) http://www.opsi.org

# Copyright (C) 2010-2019 uib GmbH

# http://www.uib.de/

# All rights reserved.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
"""
check_opsi is a nagios plugin to check an opsi-server.

@copyright: uib GmbH <info@uib.de>
@author: Erol Ueluekmen <e.ueluekmen@uib.de>
@author: Niko Wenselowski <n.wenselowski@uib.de>
@license: GNU General Public License version 2
"""

import argparse
import base64
import json
import ssl as ssl_module
import sys
from contextlib import closing
from httplib import HTTPConnection, HTTPSConnection

__version__ = "4.1.1.5"

if hasattr(ssl_module, '_create_unverified_context'):
	# We are running a new version of Python that implements PEP 476:
	# https://www.python.org/dev/peps/pep-0476/
	# To not break our expected behaviour we patch the default context
	# until we have a correct certificate check implementation.
	# TODO: remove this workaround when support for TLS1.1+ is implemented
	ssl_module._create_default_https_context = ssl_module._create_unverified_context


def main():
	possibleTasks = [
		"checkClientStatus",
		"checkShortProductStatus",
		"checkProductStatus",
		"checkDepotSyncStatus",
		"checkPluginOnClient",
		"checkOpsiWebservice",
		"checkOpsiDiskUsage",
		"checkProductLocks",
	]

	class CommaSeparatedValues(argparse.Action):
		def __call__(self, parser, namespace, values, option_string=None):
			setattr(namespace, self.dest, [v.strip() for v in values.split(',')])

	parser = argparse.ArgumentParser(description="Plugin for checking an opsi server.")
	parser.add_argument('--version', '-V', action='version', version=__version__)

	globalOptions = parser.add_argument_group(title="Global options")
	globalOptions.add_argument(
		"--host", "-H", dest="opsiHost", required=True,
		help="opsi server to connect to")
	globalOptions.add_argument(
		"--port", "-P", type=int, default=4447, help="opsi webservice port")
	globalOptions.add_argument("--user", "-u", help="opsi monitoring username")
	globalOptions.add_argument("--password", "-p", help="opsi monitoring password")
	globalOptions.add_argument(
		"--http", default=False, action="store_true",
		help=argparse.SUPPRESS
	)

	taskGroup = parser.add_argument_group(title="Task to check")
	taskGroup.add_argument(
		"--task", "-t", required=True, choices=possibleTasks,
		help="Check to execute")

	taskOptions = parser.add_argument_group(
		title="Check options",
		description=(
			"Not every check supports every option. "
			"Please refer to the manual for more information."
		)
	)
	taskOptions.add_argument("--clientId", "-c", help="ID of client in opsi")
	taskOptions.add_argument(
		'--depotIds', '-d', action=CommaSeparatedValues,
		help="depotIds (comma seperated) or 'all'")
	taskOptions.add_argument(
		'--groupIds', '-g', action=CommaSeparatedValues,
		help="groupIds (comma seperated)")
	taskOptions.add_argument(
		'--hostGroupIds', '-G', action=CommaSeparatedValues,
		help="HostGroupIds (comma seperated)")
	taskOptions.add_argument(
		'--productIds', '-e', action=CommaSeparatedValues,
		help="productIds (comma seperated)")
	taskOptions.add_argument(
		'--excl', '-x', dest="exclude", action=CommaSeparatedValues,
		help="exclude (comma seperated)")
	taskOptions.add_argument(
		"--verbose", "-v", default=None, action="store_true",
		help="generate verbose output")
	taskOptions.add_argument(
		"--timeout", "-T", help="Timeout in sec for active checks")
	taskOptions.add_argument(
		"--state", "-s", type=int, choices=[0, 1, 2, 3],
		help="actual service state ID")
	taskOptions.add_argument(
		"--plugin", help="plugin to check directly on the client")
	taskOptions.add_argument("--output", "-o", help="actual service output")
	taskOptions.add_argument("--resource", "-r", help="opsi resource to check")
	taskOptions.add_argument(
		"--strictmode", default=None, action="store_true",
		help="strictmode (checkOpsiDepotSyncStatus)")
	taskOptions.add_argument("--warning", help="warning threshold")
	taskOptions.add_argument("--critical", help="critical threshold")

	options = parser.parse_args()

	# We want to have a dict for further usage
	config = cleanConfig(vars(options))

	data = generateParams(config["task"], config)

	if data:
		response = executeCheck(config, data, options.http)
		analyseResonse(response)


def cleanConfig(config):
	"Remove all keys with None values from the dict."
	return {k: v for (k, v) in config.items() if v is not None}


def generateParams(task, config={}):
	result = {
		"task": task,
		"param": {k: v for (k, v) in config.items()}
	}

	return json.dumps(result)


def executeCheck(config, data, http_only=False):
	connectionClass = HTTPSConnection
	if http_only:
		connectionClass = HTTPConnection

	headers = {
		"User-Agent": "check_opsi/%s" % __version__
	}
	if config.get("user", None) and config.get("password", None):
		headers["Authorization"] = "Basic %s" % base64.b64encode('%s:%s' % (config["user"], config["password"]))

	with closing(connectionClass(config["opsiHost"], config["port"])) as conn:
		conn.request("POST", "/monitoring", data, headers=headers)
		response = conn.getresponse()
		return response.read()


def analyseResonse(response):
	result = json.loads(response)
	print(result.get("message", ""))
	sys.exit(int(result.get("state", 3)))


if __name__ == '__main__':
	try:
		main()
	except Exception as e:
		print(u"UNKNOWN: Failure '%s'" % str(e))
		sys.exit(3)
