opennel-pymanager/pymanager/client.py

432 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 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()