#!/usr/bin/python3 # -*- coding: utf-8 -*- # # script to send command to manager khaganat process # # Copyright (C) 2017 AleaJactaEst # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # 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, see . """ Configuration File ------------------ This script need configuration file (see below for model):: [config:client] # Define port listen (default 8000) port = 8000 # Example to generate all key : see pycertificate # key keyfile = /home/gameserver/ca/appli/private/clientkey.pem # certificate certfile = /home/gameserver/ca/appli/certs/clientcert.pem # certification to check signature ca_cert = /home/gameserver/ca/appli/certs/cachaincert.pem address = 127.0.0.1 Manipulate manager khaganat --------------------------- We can end some command to manager. **Global** * SHUTDOWN : Stop manager & Stop all programs * STARTALL : Start all programs * STATUSALL : Get status of all programs * STOPALL : Stop all programs * LIST : List all programs available **For each program** * START : Start program * STOP : Stop program * STATUS : Get status * STDOUT : Get log firstline : option to define first line we need send * STDIN : Send action (command) in stdin action : option to define which action you need send to stdin Example :: pyclient --command="START" --program="aes" -c /home/gameserver/cfg/khaganat.cfg --log="info" --show-log-console pyclient --command="STATUS" --program="aes" -c /home/gameserver/cfg/khaganat.cfg --log="debug" --show-log-console pyclient --command="STDIN" --program="aes" --stdin="help all" -c /home/gameserver/cfg/khaganat.cfg --log="debug" --show-log-console pyclient --command="STDOUT" --program="aes" --firstline=0 -c /home/gameserver/cfg/khaganat.cfg --log="debug" --show-log-console pyclient --command="STOP" --program="aes" -c /home/gameserver/cfg/khaganat.cfg --log="debug" --show-log-console pyclient --command="LIST" -c /home/gameserver/cfg/khaganat.cfg --log="debug" --show-log-console pyclient --command="SHUTDOWN" -c /home/gameserver/cfg/khaganat.cfg --log="debug" --show-log-console pyclient --command="STARTALL" -c /home/gameserver/cfg/khaganat.cfg --log="debug" --show-log-console pyclient --command="STATUSALL" -c /home/gameserver/cfg/khaganat.cfg --log="debug" --show-log-console You can use curl (to replace this script). Example :: curl -k --tlsv1.2 --cacert /home/gameserver/ca/appli/certs/cachaincert.pem --cert /home/gameserver/ca/appli/certs/clientcert.pem --key /home/gameserver/ca/appli/private/clientkey.pem -XGET "https://127.0.0.1:8000/STATUSALL" curl -k --tlsv1.2 --cacert /home/gameserver/ca/appli/certs/cachaincert.pem --cert /home/gameserver/ca/appli/certs/clientcert.pem --key /home/gameserver/ca/appli/private/clientkey.pem --header "content-type: application/json" -d '{"name": "MyProgram"}' -XGET "https://127.0.0.1:8000/STATUS """ import sys import argparse import logging import logging.config import http.client import json import socket import ssl import configparser import base64 import getpass __VERSION__ = '1.0' def cmp_to_key(): """ Compare two key (numbre or string) """ class K(object): def __init__(self, obj, *args): self.obj = obj def __lt__(self, other): try: return int(self.obj) < int(other.obj) except ValueError: return self.obj < other.obj def __gt__(self, other): try: return int(self.obj) > int(other.obj) except ValueError: return self.obj > other.obj def __eq__(self, other): try: return int(self.obj) == int(other.obj) except ValueError: return self.obj == other.obj def __le__(self, other): try: return int(self.obj) <= int(other.obj) except ValueError: return self.obj <= other.obj def __ge__(self, other): try: return int(self.obj) >= int(other.obj) except ValueError: return self.obj >= other.obj def __ne__(self, other): try: return int(self.obj) != int(other.obj) except ValueError: return self.obj != other.obj return K class HTTPConnection(http.client.HTTPConnection): """ Class HTTPConnection """ def __init__(self, host, port, timeout=10): http.client.HTTPConnection.__init__(self, host, port, timeout) class HTTPSConnectionCertificate(http.client.HTTPConnection): """ Class HTTP connection with check certificate (if certicate is defined) """ def __init__(self, key_file, cert_file, ca_cert, host='localhost', port=8000, timeout=10): """ Constructor """ logging.debug("constructor") http.client.HTTPConnection.__init__(self, host, port, timeout) self.key_file = key_file self.cert_file = cert_file self.ca_cert = ca_cert self.host = host self.port = port def connect(self): """ connect in https (and check certificate if defined) """ logging.debug("connect launched") sock = socket.create_connection((self.host, self.port), self.timeout) # If there's no CA File, don't force Server Certificate Check if self.ca_cert: logging.debug("key_file: " + str(self.key_file)) logging.debug("cert_file: " + str(self.cert_file)) logging.debug("ca_cert: " + str(self.ca_cert)) self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ca_certs=self.ca_cert, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1_2 ) else: self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, cert_reqs=ssl.CERT_NONE) class Client(): """ Class to manipulate pymanager """ def __init__(self, username, password_comand_line, password): self.username = None self.port = 8000 self.address = 'localhost' self.keyfile = None self.certfile = None self.ca_cert = None self.timeout = 10 self.method = 'http' if username: self.username = username if not password_comand_line: if password is not None: raise Exception("password in command line (but option disable to read this password).") else: self.password = getpass.getpass('Enter password:') elif password is None: raise Exception("Missing password.") else: self.password = password def load_config(self, filecfg): if filecfg is None: raise ValueError config = configparser.ConfigParser() config.read_file(filecfg) logging.debug("Sections :%s" % config.sections()) self._load_config(config) def _load_config(self, config): """ Read configuration object param: config: configuration object """ logging.debug("Sections :%s" % config.sections()) for name in config.sections(): if name == 'config:client': logging.debug("read config '%s'" % name) try: self.port = int(config[name]['port']) except KeyError: pass except (TypeError, ValueError): raise Exception("bad parameter for 'port' (in section %s)" % name) try: self.address = config[name]['address'] except KeyError: pass try: self.keyfile = config[name]['keyfile'] except KeyError: pass try: self.certfile = config[name]['certfile'] except KeyError: pass try: self.ca_cert = config[name]['ca_cert'] except KeyError: pass try: self.timeout = config[name]['timeout'] except KeyError: pass try: self.method = config[name]['method'] except KeyError: pass def send_json(self, jsonin={}, command='GET', path='/', raw_data=False, remove_color=False, show_result=True): """ send command with https & json format :param str jsonin: json input (send in data on http request) ;param str command: http command (GET, POST, ...) :param str path: path http :param bool raw_data: show result without analyze :param bool remove_color: at end string send color to disable background & color """ if self.method == 'http': conn = HTTPConnection(host=self.address, port=self.port, timeout=self.timeout) elif self.method == 'https': conn = HTTPSConnectionCertificate(host=self.address, port=self.port, key_file=self.keyfile, cert_file=self.certfile, ca_cert=self.ca_cert, timeout=self.timeout) else: logging.error("Bad value 'method' (%s)" % str(self.method)) raise ValueError conn.putrequest(command, path) out = json.dumps(jsonin) if self.username: accountpassword = "%s:%s" % (self.username, self.password) conn.putheader('Authorization', b"Basic " + base64.b64encode(bytes(accountpassword, 'UTF-8'))) conn.putheader('Content-type', 'application/json') conn.putheader('Content-length', len(out)) conn.endheaders() conn.send(bytes(out, "utf-8")) response = conn.getresponse() if raw_data: ret = response.read() if show_result: print(ret) return ret else: if remove_color: endText = '\x1b[0m' else: endText = '' if response.status != 200: logging.error("Error detected (html code:%d)" % response.status) print(response.read()) return ret = response.read().decode() msgjson = None try: msgjson = json.loads(ret) except json.JSONDecodeError: logging.error("Impossible to decode Json output") print(ret) return if show_result: for key in sorted(msgjson, key=cmp_to_key()): print("%s: %s %s" % (key, msgjson[key], endText)) return msgjson def root(command, program, stdin, firstline, fileLog, logLevel, username, password_comand_line, password, show_log_console, raw_data=False, remove_color=False, filecfg=None): # Manage log logging.getLogger('logging') numeric_level = getattr(logging, logLevel.upper(), None) if not isinstance(numeric_level, int): raise ValueError('Invalid log level: %s' % logLevel) handlers = [] if show_log_console: handlers.append(logging.StreamHandler()) if fileLog: handlers.append(logging.FileHandler(fileLog.name)) logging.basicConfig(handlers=handlers, level=numeric_level, format='%(asctime)s %(levelname)s [pid:%(process)d] [%(funcName)s:%(lineno)d] %(message)s') client = Client(username, password_comand_line, password) client.load_config(filecfg) # Send command if command == 'START' or command == 'STOP': client.send_json({'name': program}, 'POST', "/" + command) elif command == 'STATUS': client.send_json({'name': program}, 'GET', "/" + command) elif command == 'STDIN': client.send_json({'name': program, 'action': stdin}, 'POST', "/" + command) elif command == 'STDOUT': client.send_json({'name': program, 'first-line': firstline}, 'GET', "/" + command) elif command == 'LIST': client.send_json({}, 'GET', "/" + command) elif command == 'SHUTDOWN' or command == 'STARTALL' or command == 'STOPALL': client.send_json({}, 'POST', "/" + command) elif command == 'STATUSALL': client.send_json({}, 'GET', "/" + command) else: logging.error("command unknown (%s)" % command) def main(args=sys.argv[1:]): """ Main function :param list args: all arguments ('--help, '--version', ...) """ parser = argparse.ArgumentParser(description='Manipulate khaganat process') parser.add_argument('--version', action='version', version='%(prog)s ' + __VERSION__) parser.add_argument('--show-log-console', action='store_true', help='show message in console', default=False) parser.add_argument('--filelog', type=argparse.FileType('wt'), default=None, help='log file') parser.add_argument('--log', default='INFO', help='log level [DEBUG, INFO, WARNING, ERROR') parser.add_argument('--command', help='command send to khganat', default='/STATUS') parser.add_argument('--program', help='program khaganat id', default='aes') parser.add_argument('--stdin', help='action send to stdin', default='') parser.add_argument('--firstline', type=int, help='define fistline read for log command', default=0) parser.add_argument('--raw-data', action='store_true', help='show raw message', default=False) parser.add_argument('--keep-color', action='store_true', help='some message have color define, by default we reset the color ' '(this option keep current color state)', default=False) parser.add_argument('-c', '--conf', type=argparse.FileType('r'), default='khaganat.cfg', help='configuration file') parser.add_argument('-b', '--password-comand-line', action='store_true', help='Use the password from the command line rather than prompting for it.', default=False) parser.add_argument('username', type=str, nargs='?', default=None) parser.add_argument('password', type=str, nargs='?') param = parser.parse_args(args) root(stdin=param.stdin, firstline=param.firstline, command=param.command, program=param.program, fileLog=param.filelog, logLevel=param.log, show_log_console=param.show_log_console, raw_data=param.raw_data, remove_color=not param.keep_color, filecfg=param.conf, username=param.username, password_comand_line=param.password_comand_line, password=param.password) if __name__ == '__main__': main()