commit df999da11700164d32b4aaf278ba83e697b169ff Author: Emmanuel Garette Date: Fri Nov 29 09:02:10 2019 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c16bd4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Backup and swap files +*~ +*# +*.swp +*.pyc +__pycache__ diff --git a/script/cucchiaiata b/script/cucchiaiata new file mode 100755 index 0000000..1ff1e27 --- /dev/null +++ b/script/cucchiaiata @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Zephir-cmd-input script +""" +from sys import exit +from traceback import print_exc +from cucchiaiata import Parser, config +from cucchiaiata.i18n import _ + + +def main(): + # if sys.argv[1] == 'config.session.server.configure' or \ + # sys.argv[1] == 'config.session.servermodel.configure': + # configuration(sys.argv[1]) + # else: + try: + parser = Parser() + print(parser.get()) + except Exception as err: + if config.debug: + print_exc() + print(_('Error: {}').format(err)) + exit(1) + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass diff --git a/src/cucchiaiata/__init__.py b/src/cucchiaiata/__init__.py new file mode 100644 index 0000000..dc09ff8 --- /dev/null +++ b/src/cucchiaiata/__init__.py @@ -0,0 +1,5 @@ +from .parser import Parser +from .config import config + +__all__ = ('Parser', 'config') +__version__ = "0.0.1" diff --git a/src/cucchiaiata/config.py b/src/cucchiaiata/config.py new file mode 100644 index 0000000..924bcea --- /dev/null +++ b/src/cucchiaiata/config.py @@ -0,0 +1,44 @@ +"""Risotto command line configuration parser +""" +from os.path import isfile, expanduser, join +from yaml import load, SafeLoader, YAMLError + +from .i18n import _ + +class Config: + def __init__(self): + config_file = join(expanduser("~"), '.zephir-config.yaml') + if not isfile(config_file): + print(_('Attention, there is no configuration file')) + url = input(_('Address to Risotto server: ')) + version = input(_('Risotto API\'s version (default: "v1"): ')) + if not version: + version = "v1" + yaml_template = f"""url: {url} +version: {version}""" + + with open(config_file, 'w') as fh: + fh.write(yaml_template) + print(_('config file {} created').format(config_file)) + + with open(config_file, 'r') as stream: + try: + config = load(stream, Loader=SafeLoader) + except YAMLError as err: + raise Exception(_('Error when creating the config file {}').format(err)) + + self.url = config['url'] + self.version = config['version'] + self.debug = 'debug' in config + self.remote_url = 'http://{}/api/{}'.format(self.url, self.version) + self.token_file = join(expanduser("~"), '.zephir-client.jwt.token') + self.indent = config.get('indent', 2) + + +TO_JSON = {'config.session.server.get': ['content'], + 'config.session.servermodel.get': ['content'], + 'server.describe': ['configuration'], + 'server.exec.describe': ['return']} + + +config = Config() diff --git a/src/cucchiaiata/i18n.py b/src/cucchiaiata/i18n.py new file mode 100644 index 0000000..b33691a --- /dev/null +++ b/src/cucchiaiata/i18n.py @@ -0,0 +1,2 @@ +def _(msg): + return msg diff --git a/src/cucchiaiata/parser.py b/src/cucchiaiata/parser.py new file mode 100644 index 0000000..ca9152a --- /dev/null +++ b/src/cucchiaiata/parser.py @@ -0,0 +1,176 @@ +"""Zephir Client library +""" +from os.path import isfile +from requests import get, post +from json import dumps, loads +from typing import Dict +# import warnings +# from urllib3.exceptions import InsecureRequestWarning + +from tiramisu_cmdline_parser import TiramisuCmdlineParser +from argparse import RawDescriptionHelpFormatter +from tiramisu_api import Config + +from .config import config, TO_JSON + + +# warnings.simplefilter('ignore', InsecureRequestWarning) + + +class CucchiaiataParser(TiramisuCmdlineParser): + def print_help(self, *args, **kwargs): + if not self.config.option('message').value.get(): + self.remove_empty_od = False + return super().print_help(*args, **kwargs) + + +class Parser: + def __init__(self): + # retrieve config's option + self.cucchiaiata_config = config + self.remote_url = self.cucchiaiata_config.remote_url + self.remote_config = self.remote_json_to_config() + + # build a tiramisu parser and parse argument + parser = CucchiaiataParser(self.remote_config, + fullpath=False, + remove_empty_od=True, + display_modified_value=False, + formatter_class=RawDescriptionHelpFormatter) + parser.parse_args() + + def get(self): + # get current message + message = self.remote_config.option('message').value.get() + payload = self.get_payload(message) + # send message + result = self.send_data(message, + payload) + + # convert some results (file) to json + if message in TO_JSON: + if isinstance(result, list): + for res in result: + _to_json(res) + else: + _to_json(result) + + return dumps(result, indent=config.indent) + + def remote_json_to_config(self, + headers=None, + config_type=Config): + "retrieves the remote config from the distant api description" + req = get(self.remote_url, + headers=headers, + verify=False) + code = req.status_code + if code != 200: + err = get_error_from_http(req) + raise Exception('unable to load url ({}): {} ({})'.format(self.remote_url, + err, + code)) + json = req.json() + return config_type(json) + + def send_data(self, + message: str, + payload: Dict): + # get stored token if available + if isfile(self.cucchiaiata_config.token_file): + token = open(self.cucchiaiata_config.token_file).read() + else: + token = '' + final_url = '{}/{}'.format(self.remote_url, message) + headers = {'Authorization':'Bearer {}'.format(token)} + ret = post(final_url, + data=dumps(payload), + headers=headers, + # FIXME + verify=False) + if ret.status_code != 200: + raise Exception(ret.text) + response = ret.json() + if 'error' in response: + if 'reason' in response['error']['kwargs']: + raise Exception("{}".format(response['error']['kwargs']['reason'])) + raise Exception('erreur inconnue') + else: + return(response['response']) + + def _to_json(self, res): + for key, value in res.items(): + if key in TO_JSON[message]: + res[key] = loads(value) + + def get_payload(self, + message: str): + # remove symlinkoption and default value from payload + payload = {} + for option in self.remote_config.option(message).list(): + if not option.owner.isdefault() and not option.option.issymlinkoption(): + payload[option.option.name()] = option.value.get() + return payload + + def get_error_from_http(self, + req): + try: + json = req.json() + err = json['error']['kwargs']['reason'] + except: + err = req.text + return err + + +def configure_server(server, session_id, version, message, reporter=None): + url = 'https://{}/config/{}/api'.format(server, session_id) + token = open(config.token_file).read() + headers = {'Authorization':'Bearer {}'.format(token)} + tconfig = remote_json_to_config(url, headers, ConfigConfigurationAPI) + tconfig.version = version + tconfig.message = message + tconfig.url = server + tconfig.session_id = session_id + tconfig.reporter = reporter + return tconfig + + + +def configuration(config, + session_id, + configure): + parameters = sys.argv.copy() + # get sessionid index session_id + try: + index = parameters.index('-s') + except ValueError: + try: + index = parameters.index('--sessionid') + except ValueError: + remote_config = remote_json_to_config(config.remote_url) + parser = cmdline_factory(remote_config) + parser.print_help() + sys.exit(1) + parameters.pop(index) + session_id = parameters.pop(index) + # remove config.session.server.configure + parameters.pop(1) + # remove commandline + prog = parameters.pop(0) + try: + conf = configure_server(config.url, + session_id, + config.version, + configure) + parser = TiramisuCmdlineParser(conf, + prog, + unrestraint=True, + fullpath=True) + parser.parse_args(parameters, + valid_mandatory=False) + config.send() + except Exception as err: + if config.debug: + print_exc() + print(_("Error: {}").format(err)) + sys.exit(1)