407 lines
16 KiB
Python
Executable file
407 lines
16 KiB
Python
Executable file
#!/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 <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
|
|
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 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
|
|
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
|
|
|
|
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
|
|
"""
|
|
conn = HTTPSConnectionCertificate(host=self.address,
|
|
port=self.port,
|
|
key_file=self.keyfile,
|
|
cert_file=self.certfile,
|
|
ca_cert=self.ca_cert,
|
|
timeout=self.timeout)
|
|
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()
|