diff --git a/bin/test-moulinette-api b/bin/test-moulinette-api new file mode 100755 index 00000000..0a7602f9 --- /dev/null +++ b/bin/test-moulinette-api @@ -0,0 +1,188 @@ +#! /usr/bin/python +# -*- coding: utf-8 -*- + +import os +import sys +import argparse + +# Either we are in a development environment or not +IN_DEVEL = False + +# Default server configuration +DEFAULT_HOST = 'localhost' +DEFAULT_PORT = 6787 + +# Level for which loggers will log +LOGGERS_LEVEL = 'INFO' + +# Handlers that will be used by loggers +# - file: log to the file LOG_DIR/LOG_FILE +# - api: serve logs through the api +# - console: log to stderr +LOGGERS_HANDLERS = ['file', 'api'] + +# Directory and file to be used by logging +LOG_DIR = '/var/log/test' +LOG_FILE = 'test-api.log' + +# Check and load - as needed - development environment +if not __file__.startswith('/usr/'): + IN_DEVEL = True +if IN_DEVEL: + basedir = os.path.abspath('%s/../' % os.path.dirname(__file__)) + if os.path.isdir(os.path.join(basedir, 'moulinette')): + sys.path.insert(0, basedir) + LOG_DIR = os.path.join(basedir, 'log') + + +import moulinette +from moulinette.actionsmap import ActionsMap +from moulinette.interfaces.cli import colorize + + +# Initialization & helpers functions ----------------------------------- + +def _die(message, title='Error:'): + """Print error message and exit""" + print('%s %s' % (colorize(title, 'red'), message)) + sys.exit(1) + +def _parse_api_args(): + """Parse main arguments for the api""" + parser = argparse.ArgumentParser(add_help=False, + description="Run the YunoHost API to manage your server.", + ) + srv_group = parser.add_argument_group('server configuration') + srv_group.add_argument('-h', '--host', + action='store', default=DEFAULT_HOST, + help="Host to listen on (default: %s)" % DEFAULT_HOST, + ) + srv_group.add_argument('-p', '--port', + action='store', default=DEFAULT_PORT, type=int, + help="Port to listen on (default: %d)" % DEFAULT_PORT, + ) + srv_group.add_argument('--no-websocket', + action='store_true', default=True, dest='use_websocket', + help="Serve without WebSocket support, used to handle " + "asynchronous responses such as the messages", + ) + glob_group = parser.add_argument_group('global arguments') + glob_group.add_argument('--no-cache', + action='store_false', default=True, dest='use_cache', + help="Don't use actions map cache", + ) + glob_group.add_argument('--debug', + action='store_true', default=False, + help="Set log level to DEBUG", + ) + glob_group.add_argument('--verbose', + action='store_true', default=False, + help="Be verbose in the output", + ) + glob_group.add_argument('--help', + action='help', help="Show this help message and exit", + ) + + return parser.parse_args() + +def _init_moulinette(use_websocket=True, debug=False, verbose=False): + """Configure logging and initialize the moulinette""" + # Define loggers handlers + handlers = set(LOGGERS_HANDLERS) + if not use_websocket and 'api' in handlers: + handlers.remove('api') + if verbose and 'console' not in handlers: + handlers.add('console') + root_handlers = handlers - set(['api']) + + # Define loggers level + level = LOGGERS_LEVEL + if debug: + level = 'DEBUG' + + # Custom logging configuration + logging = { + 'version': 1, + 'disable_existing_loggers': True, + 'formatters': { + 'console': { + 'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' + }, + 'precise': { + 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' + }, + }, + 'filters': { + 'action': { + '()': 'moulinette.utils.log.ActionFilter', + }, + }, + 'handlers': { + 'api': { + 'class': 'moulinette.interfaces.api.APIQueueHandler', + }, + 'file': { + 'class': 'logging.handlers.WatchedFileHandler', + 'formatter': 'precise', + 'filename': '%s/%s' % (LOG_DIR, LOG_FILE), + 'filters': ['action'], + }, + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'console', + 'stream': 'ext://sys.stdout', + 'filters': ['action'], + }, + }, + 'loggers': { + 'yunohost': { + 'level': level, + 'handlers': handlers, + 'propagate': False, + }, + 'moulinette': { + 'level': level, + 'handlers': [], + 'propagate': True, + }, + }, + 'root': { + 'level': level, + 'handlers': root_handlers, + }, + } + + # Create log directory + if not os.path.isdir(LOG_DIR): + try: + os.makedirs(LOG_DIR, 0750) + except os.error as e: + _die(str(e)) + + # Initialize moulinette + moulinette.init(logging_config=logging, _from_source=IN_DEVEL) + +def _retrieve_namespaces(): + """Return the list of namespaces to load""" + ret = ['test'] + for n in ActionsMap.get_namespaces(): + # Append YunoHost modules + if n.startswith('test_'): + ret.append(n) + return ret + + +# Main action ---------------------------------------------------------- + +if __name__ == '__main__': + opts = _parse_api_args() + _init_moulinette(opts.use_websocket, opts.debug, opts.verbose) + + # Run the server + from yunohost.utils.packages import ynh_packages_version + ret = moulinette.api( + _retrieve_namespaces(), + host=opts.host, port=opts.port, routes={ + }, use_cache=opts.use_cache, use_websocket=opts.use_websocket + ) + sys.exit(ret) diff --git a/debian/test-moulinette-api.default b/debian/test-moulinette-api.default new file mode 100644 index 00000000..b6a9e5a9 --- /dev/null +++ b/debian/test-moulinette-api.default @@ -0,0 +1,4 @@ +# Override yunohost-api options. +# Example to log debug: DAEMON_OPTS="--debug" +# +#DAEMON_OPTS="" diff --git a/debian/test-moulinette-api.init b/debian/test-moulinette-api.init new file mode 100644 index 00000000..36453aa1 --- /dev/null +++ b/debian/test-moulinette-api.init @@ -0,0 +1,132 @@ +#! /bin/sh + +### BEGIN INIT INFO +# Provides: yunohost-api +# Required-Start: $local_fs $remote_fs $network $syslog +# Required-Stop: $local_fs $remote_fs $network $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Manage YunoHost API Server +# Description: Manage YunoHost API Server +### END INIT INFO + +set -e + +DESC="test API Server" +NAME="test-moulinette-api" +DAEMON=/usr/bin/$NAME +DAEMON_OPTS="" +PATH=/sbin:/usr/sbin:/bin:/usr/bin +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME +LOGFILE=/var/log/$NAME.log + +# Include yunohost-api defaults if available +if [ -r /etc/default/test-moulinette-api ]; then + . /etc/default/test-moulinette-api +fi + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --background --make-pidfile --quiet --no-close \ + --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_OPTS >>$LOGFILE 2>&1 \ + || return 2 +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --oknodo --pidfile $PIDFILE + RETVAL="$?" + + sleep 1 + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # Send a SIGHUP to reload the daemon. + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + reload) + log_daemon_msg "Reloading $DESC" "$NAME" + do_reload + log_end_msg $? + ;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/debian/test-moulinette-api.service b/debian/test-moulinette-api.service new file mode 100644 index 00000000..cdf63bb4 --- /dev/null +++ b/debian/test-moulinette-api.service @@ -0,0 +1,15 @@ +[Unit] +Description=Test API Server +After=network.target + +[Service] +Type=simple +Environment=DAEMON_OPTS= +EnvironmentFile=-/etc/default/test-moulinette-api +ExecStart=/usr/bin/test-moulinette-api $DAEMON_OPTS +ExecReload=/bin/kill -HUP $MAINPID +Restart=always +RestartSec=1 + +[Install] +WantedBy=multi-user.target