mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge pull request #115 from YunoHost/logging
Make use of logging instead of msignals.display
This commit is contained in:
commit
9389797ceb
9 changed files with 328 additions and 226 deletions
165
bin/yunohost
165
bin/yunohost
|
@ -7,27 +7,28 @@ import os
|
|||
# Either we are in a development environment or not
|
||||
IN_DEVEL = False
|
||||
|
||||
# Either cache has to be used inside the moulinette or not
|
||||
USE_CACHE = True
|
||||
|
||||
# Either the output has to be encoded as a JSON encoded string or not
|
||||
PRINT_JSON = False
|
||||
|
||||
# Either the output has to printed for scripting usage or not
|
||||
PRINT_PLAIN = False
|
||||
|
||||
# Level for which loggers will log
|
||||
LOGGERS_LEVEL = 'INFO'
|
||||
TTY_LOG_LEVEL = 'SUCCESS'
|
||||
|
||||
# Handlers that will be used by loggers
|
||||
# - file: log to the file LOG_DIR/LOG_FILE
|
||||
# - console: log to stderr
|
||||
LOGGERS_HANDLERS = ['file']
|
||||
# - tty: log to current tty
|
||||
LOGGERS_HANDLERS = ['file', 'tty']
|
||||
|
||||
# Directory and file to be used by logging
|
||||
LOG_DIR = '/var/log/yunohost'
|
||||
LOG_FILE = 'yunohost-cli.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')
|
||||
|
||||
|
||||
# Initialization & helpers functions -----------------------------------
|
||||
|
||||
|
@ -40,83 +41,114 @@ def _die(message, title='Error:'):
|
|||
print('%s %s' % (colorize(title, 'red'), message))
|
||||
sys.exit(1)
|
||||
|
||||
def _check_in_devel():
|
||||
"""Check and load if needed development environment"""
|
||||
global IN_DEVEL, LOG_DIR
|
||||
basedir = os.path.abspath('%s/../' % os.path.dirname(__file__))
|
||||
if os.path.isdir('%s/moulinette' % basedir) and not IN_DEVEL:
|
||||
# Add base directory to python path
|
||||
sys.path.insert(0, basedir)
|
||||
def _parse_cli_args():
|
||||
"""Parse additional arguments for the cli"""
|
||||
import argparse
|
||||
|
||||
# Update global variables
|
||||
IN_DEVEL = True
|
||||
LOG_DIR = '%s/log' % basedir
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--no-cache',
|
||||
action='store_false', default=True, dest='use_cache',
|
||||
help="Don't use actions map cache",
|
||||
)
|
||||
parser.add_argument('--output-as',
|
||||
choices=['json', 'plain'], default=None,
|
||||
help="Output result in another format",
|
||||
)
|
||||
parser.add_argument('--debug',
|
||||
action='store_true', default=False,
|
||||
help="Log and print debug messages",
|
||||
)
|
||||
parser.add_argument('--verbose',
|
||||
action='store_true', default=False,
|
||||
help="Be more verbose in the output",
|
||||
)
|
||||
parser.add_argument('--quiet',
|
||||
action='store_true', default=False,
|
||||
help="Don't produce any output",
|
||||
)
|
||||
# deprecated arguments
|
||||
parser.add_argument('--plain',
|
||||
action='store_true', default=False, help=argparse.SUPPRESS
|
||||
)
|
||||
parser.add_argument('--json',
|
||||
action='store_true', default=False, help=argparse.SUPPRESS
|
||||
)
|
||||
|
||||
def _parse_argv():
|
||||
"""Parse additional arguments and return remaining ones"""
|
||||
argv = list(sys.argv)
|
||||
argv.pop(0)
|
||||
opts, args = parser.parse_known_args()
|
||||
|
||||
if '--no-cache' in argv:
|
||||
global USE_CACHE
|
||||
USE_CACHE = False
|
||||
argv.remove('--no-cache')
|
||||
if '--json' in argv:
|
||||
global PRINT_JSON
|
||||
PRINT_JSON = True
|
||||
argv.remove('--json')
|
||||
if '--plain' in argv:
|
||||
global PRINT_PLAIN
|
||||
PRINT_PLAIN = True
|
||||
argv.remove('--plain')
|
||||
if '--debug' in argv:
|
||||
global LOGGERS_LEVEL
|
||||
LOGGERS_LEVEL = 'DEBUG'
|
||||
argv.remove('--debug')
|
||||
if '--verbose' in argv:
|
||||
global LOGGERS_HANDLERS
|
||||
if 'console' not in LOGGERS_HANDLERS:
|
||||
LOGGERS_HANDLERS.append('console')
|
||||
argv.remove('--verbose')
|
||||
return argv
|
||||
# output compatibility
|
||||
if opts.plain:
|
||||
opts.output_as = 'plain'
|
||||
elif opts.json:
|
||||
opts.output_as = 'json'
|
||||
|
||||
def _init_moulinette():
|
||||
return (parser, opts, args)
|
||||
|
||||
def _init_moulinette(debug=False, verbose=False, quiet=False):
|
||||
"""Configure logging and initialize the moulinette"""
|
||||
from moulinette import init
|
||||
|
||||
# Define loggers handlers
|
||||
handlers = set(LOGGERS_HANDLERS)
|
||||
if quiet and 'tty' in handlers:
|
||||
handlers.remove('tty')
|
||||
elif verbose and 'tty' not in handlers:
|
||||
handlers.append('tty')
|
||||
root_handlers = handlers - set(['tty'])
|
||||
|
||||
# Define loggers level
|
||||
level = LOGGERS_LEVEL
|
||||
tty_level = TTY_LOG_LEVEL
|
||||
if verbose:
|
||||
tty_level = 'INFO'
|
||||
if debug:
|
||||
tty_level = level = 'DEBUG'
|
||||
|
||||
# Custom logging configuration
|
||||
logging = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': True,
|
||||
'formatters': {
|
||||
'simple': {
|
||||
'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s - %(message)s'
|
||||
'tty-debug': {
|
||||
'format': '%(relativeCreated)-4d %(fmessage)s'
|
||||
},
|
||||
'precise': {
|
||||
'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(message)s'
|
||||
'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s'
|
||||
},
|
||||
},
|
||||
'filters': {
|
||||
'action': {
|
||||
'()': 'moulinette.utils.log.ActionFilter',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple',
|
||||
'stream': 'ext://sys.stderr',
|
||||
'tty': {
|
||||
'level': tty_level,
|
||||
'class': 'moulinette.interfaces.cli.TTYHandler',
|
||||
'formatter': 'tty-debug' if debug else '',
|
||||
},
|
||||
'file': {
|
||||
'class': 'logging.FileHandler',
|
||||
'formatter': 'precise',
|
||||
'filename': '%s/%s' % (LOG_DIR, LOG_FILE),
|
||||
'filters': ['action'],
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'moulinette': {
|
||||
'level': LOGGERS_LEVEL,
|
||||
'handlers': LOGGERS_HANDLERS,
|
||||
},
|
||||
'yunohost': {
|
||||
'level': LOGGERS_LEVEL,
|
||||
'handlers': LOGGERS_HANDLERS,
|
||||
'level': level,
|
||||
'handlers': handlers,
|
||||
'propagate': False,
|
||||
},
|
||||
'moulinette': {
|
||||
'level': level,
|
||||
'handlers': [],
|
||||
'propagate': True,
|
||||
},
|
||||
},
|
||||
'root': {
|
||||
'level': level,
|
||||
'handlers': root_handlers,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -144,9 +176,8 @@ def _retrieve_namespaces():
|
|||
# Main action ----------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
_check_in_devel()
|
||||
args = _parse_argv()
|
||||
_init_moulinette()
|
||||
parser, opts, args = _parse_cli_args()
|
||||
_init_moulinette(opts.debug, opts.verbose, opts.quiet)
|
||||
|
||||
# Check that YunoHost is installed
|
||||
if not os.path.isfile('/etc/yunohost/installed') and \
|
||||
|
@ -163,6 +194,8 @@ if __name__ == '__main__':
|
|||
|
||||
# Execute the action
|
||||
from moulinette import cli
|
||||
ret = cli(_retrieve_namespaces(), args, use_cache=USE_CACHE,
|
||||
print_json=PRINT_JSON, print_plain=PRINT_PLAIN)
|
||||
ret = cli(_retrieve_namespaces(), args,
|
||||
use_cache=opts.use_cache, output_as=opts.output_as,
|
||||
parser_kwargs={'top_parser': parser}
|
||||
)
|
||||
sys.exit(ret)
|
||||
|
|
167
bin/yunohost-api
167
bin/yunohost-api
|
@ -7,24 +7,32 @@ import os.path
|
|||
# Either we are in a development environment or not
|
||||
IN_DEVEL = False
|
||||
|
||||
# Either cache has to be used inside the moulinette or not
|
||||
USE_CACHE = True
|
||||
|
||||
# Either WebSocket has to be installed by the moulinette or not
|
||||
USE_WEBSOCKET = True
|
||||
# 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']
|
||||
LOGGERS_HANDLERS = ['file', 'api']
|
||||
|
||||
# Directory and file to be used by logging
|
||||
LOG_DIR = '/var/log/yunohost'
|
||||
LOG_FILE = 'yunohost-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')
|
||||
|
||||
|
||||
# Initialization & helpers functions -----------------------------------
|
||||
|
||||
|
@ -37,79 +45,112 @@ def _die(message, title='Error:'):
|
|||
print('%s %s' % (colorize(title, 'red'), message))
|
||||
sys.exit(1)
|
||||
|
||||
def _check_in_devel():
|
||||
"""Check and load if needed development environment"""
|
||||
global IN_DEVEL, LOG_DIR
|
||||
basedir = os.path.abspath('%s/../' % os.path.dirname(__file__))
|
||||
if os.path.isdir('%s/moulinette' % basedir) and not IN_DEVEL:
|
||||
# Add base directory to python path
|
||||
sys.path.insert(0, basedir)
|
||||
def _parse_api_args():
|
||||
"""Parse main arguments for the api"""
|
||||
import argparse
|
||||
|
||||
# Update global variables
|
||||
IN_DEVEL = True
|
||||
LOG_DIR = '%s/log' % basedir
|
||||
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",
|
||||
)
|
||||
|
||||
def _parse_argv():
|
||||
"""Parse additional arguments and return remaining ones"""
|
||||
argv = list(sys.argv)
|
||||
argv.pop(0)
|
||||
return parser.parse_args()
|
||||
|
||||
if '--no-cache' in argv:
|
||||
global USE_CACHE
|
||||
USE_CACHE = False
|
||||
argv.remove('--no-cache')
|
||||
if '--no-websocket' in argv:
|
||||
global USE_WEBSOCKET
|
||||
USE_WEBSOCKET = False
|
||||
argv.remove('--no-websocket')
|
||||
if '--debug' in argv:
|
||||
global LOGGERS_LEVEL
|
||||
LOGGERS_LEVEL = 'DEBUG'
|
||||
argv.remove('--debug')
|
||||
if '--verbose' in argv:
|
||||
global LOGGERS_HANDLERS
|
||||
if 'console' not in LOGGERS_HANDLERS:
|
||||
LOGGERS_HANDLERS.append('console')
|
||||
argv.remove('--verbose')
|
||||
return argv
|
||||
|
||||
def _init_moulinette():
|
||||
def _init_moulinette(use_websocket=True, debug=False, verbose=False):
|
||||
"""Configure logging and initialize the moulinette"""
|
||||
from moulinette import init
|
||||
|
||||
# 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': {
|
||||
'simple': {
|
||||
'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s - %(message)s'
|
||||
'console': {
|
||||
'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s'
|
||||
},
|
||||
'precise': {
|
||||
'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(message)s'
|
||||
'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s'
|
||||
},
|
||||
},
|
||||
'filters': {
|
||||
'action': {
|
||||
'()': 'moulinette.utils.log.ActionFilter',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple',
|
||||
'stream': 'ext://sys.stderr',
|
||||
'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': {
|
||||
'moulinette': {
|
||||
'level': LOGGERS_LEVEL,
|
||||
'handlers': LOGGERS_HANDLERS,
|
||||
},
|
||||
'yunohost': {
|
||||
'level': LOGGERS_LEVEL,
|
||||
'handlers': LOGGERS_HANDLERS,
|
||||
'level': level,
|
||||
'handlers': handlers,
|
||||
'propagate': False,
|
||||
},
|
||||
'moulinette': {
|
||||
'level': level,
|
||||
'handlers': [],
|
||||
'propagate': True,
|
||||
},
|
||||
},
|
||||
'root': {
|
||||
'level': level,
|
||||
'handlers': root_handlers,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -150,20 +191,18 @@ def is_installed():
|
|||
# Main action ----------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
_check_in_devel()
|
||||
_parse_argv()
|
||||
_init_moulinette()
|
||||
opts = _parse_api_args()
|
||||
_init_moulinette(opts.use_websocket, opts.debug, opts.verbose)
|
||||
|
||||
from moulinette import (api, MoulinetteError)
|
||||
# Run the server
|
||||
from moulinette import api, MoulinetteError
|
||||
from yunohost import get_versions
|
||||
try:
|
||||
# Run the server
|
||||
api(_retrieve_namespaces(), port=6787,
|
||||
ret = api(_retrieve_namespaces(),
|
||||
host=opts.host, port=opts.port,
|
||||
routes={
|
||||
('GET', '/installed'): is_installed,
|
||||
('GET', '/version'): get_versions,
|
||||
},
|
||||
use_cache=USE_CACHE, use_websocket=USE_WEBSOCKET)
|
||||
except MoulinetteError as e:
|
||||
_die(e.strerror, m18n.g('error'))
|
||||
sys.exit(0)
|
||||
use_cache=opts.use_cache, use_websocket=opts.use_websocket
|
||||
)
|
||||
sys.exit(ret)
|
||||
|
|
|
@ -1355,11 +1355,15 @@ hook:
|
|||
action_help: Execute hook from a file with arguments
|
||||
api: GET /hook
|
||||
arguments:
|
||||
file:
|
||||
help: Script to execute
|
||||
path:
|
||||
help: Path of the script to execute
|
||||
-a:
|
||||
full: --args
|
||||
help: Arguments to pass to the script
|
||||
--raise-on-error:
|
||||
help: Raise if the script returns a non-zero exit code
|
||||
action: store_true
|
||||
-q:
|
||||
full: --no-trace
|
||||
help: Do not print each command that will be executed
|
||||
action: store_true
|
||||
|
|
12
data/bash-completion.d/yunohost
Normal file
12
data/bash-completion.d/yunohost
Normal file
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# Bash completion for yunohost
|
||||
#
|
||||
|
||||
_python_argcomplete() {
|
||||
local IFS=''
|
||||
COMPREPLY=( $(IFS="$IFS" COMP_LINE="$COMP_LINE" COMP_POINT="$COMP_POINT" _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" _ARGCOMPLETE=1 "$1" 8>&1 9>&2 1>/dev/null 2>/dev/null) )
|
||||
if [[ $? != 0 ]]; then
|
||||
unset COMPREPLY
|
||||
fi
|
||||
}
|
||||
complete -o nospace -o default -F _python_argcomplete "yunohost"
|
1
debian/install
vendored
1
debian/install
vendored
|
@ -1,4 +1,5 @@
|
|||
bin/* /usr/bin/
|
||||
data/bash-completion.d/yunohost /etc/bash_completion.d/
|
||||
data/actionsmap/* /usr/share/moulinette/actionsmap/
|
||||
data/hooks/* /usr/share/yunohost/hooks/
|
||||
data/other/* /usr/share/yunohost/yunohost-config/moulinette/
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
"mysql_db_initialized" : "MySQL database successfully initialized",
|
||||
"extracting" : "Extracting...",
|
||||
"downloading" : "Downloading...",
|
||||
"executing_script": "Executing script...",
|
||||
"executing_script": "Executing script '{script:s}'...",
|
||||
"executing_command": "Executing command '{command:s}'...",
|
||||
"done" : "Done.",
|
||||
|
||||
"path_removal_failed" : "Unable to remove path {:s}",
|
||||
|
@ -87,6 +88,7 @@
|
|||
"hook_choice_invalid" : "Invalid choice '{:s}'",
|
||||
"hook_argument_missing" : "Missing argument '{:s}'",
|
||||
"hook_exec_failed" : "Script execution failed",
|
||||
"hook_exec_not_terminated" : "Script execution hasn’t terminated",
|
||||
|
||||
"mountpoint_unknown" : "Unknown mountpoint",
|
||||
"unit_unknown" : "Unknown unit '{:s}'",
|
||||
|
@ -118,7 +120,7 @@
|
|||
"service_no_log" : "No log to display for service '{:s}'",
|
||||
"service_cmd_exec_failed" : "Unable to execute command '{:s}'",
|
||||
"services_configured": "Configuration successfully generated",
|
||||
"service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file)",
|
||||
"service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file). Here are the differences:\n{diff:s}",
|
||||
"no_such_conf_file": "Unable to copy the file {file:s}: the file does not exist",
|
||||
"service_add_configuration": "Adding the configuration file {file:s}",
|
||||
|
||||
|
|
|
@ -83,8 +83,7 @@ def firewall_allow(protocol, port, ipv4_only=False, ipv6_only=False,
|
|||
firewall[i][p].append(port)
|
||||
else:
|
||||
ipv = "IPv%s" % i[3]
|
||||
msignals.display(m18n.n('port_already_opened', port, ipv),
|
||||
'warning')
|
||||
logger.warning(m18n.n('port_already_opened', port, ipv))
|
||||
# Add port forwarding with UPnP
|
||||
if not no_upnp and port not in firewall['uPnP'][p]:
|
||||
firewall['uPnP'][p].append(port)
|
||||
|
@ -141,8 +140,7 @@ def firewall_disallow(protocol, port, ipv4_only=False, ipv6_only=False,
|
|||
firewall[i][p].remove(port)
|
||||
else:
|
||||
ipv = "IPv%s" % i[3]
|
||||
msignals.display(m18n.n('port_already_closed', port, ipv),
|
||||
'warning')
|
||||
logger.warning(m18n.n('port_already_closed', port, ipv))
|
||||
# Remove port forwarding with UPnP
|
||||
if upnp and port in firewall['uPnP'][p]:
|
||||
firewall['uPnP'][p].remove(port)
|
||||
|
@ -214,9 +212,9 @@ def firewall_reload(skip_upnp=False):
|
|||
try:
|
||||
process.check_output("iptables -L")
|
||||
except process.CalledProcessError as e:
|
||||
logger.info('iptables seems to be not available, it outputs:\n%s',
|
||||
prependlines(e.output.rstrip(), '> '))
|
||||
msignals.display(m18n.n('iptables_unavailable'), 'info')
|
||||
logger.debug('iptables seems to be not available, it outputs:\n%s',
|
||||
prependlines(e.output.rstrip(), '> '))
|
||||
logger.warning(m18n.n('iptables_unavailable'))
|
||||
else:
|
||||
rules = [
|
||||
"iptables -F",
|
||||
|
@ -243,9 +241,9 @@ def firewall_reload(skip_upnp=False):
|
|||
try:
|
||||
process.check_output("ip6tables -L")
|
||||
except process.CalledProcessError as e:
|
||||
logger.info('ip6tables seems to be not available, it outputs:\n%s',
|
||||
prependlines(e.output.rstrip(), '> '))
|
||||
msignals.display(m18n.n('ip6tables_unavailable'), 'info')
|
||||
logger.debug('ip6tables seems to be not available, it outputs:\n%s',
|
||||
prependlines(e.output.rstrip(), '> '))
|
||||
logger.warning(m18n.n('ip6tables_unavailable'))
|
||||
else:
|
||||
rules = [
|
||||
"ip6tables -F",
|
||||
|
@ -282,9 +280,9 @@ def firewall_reload(skip_upnp=False):
|
|||
os.system("service fail2ban restart")
|
||||
|
||||
if errors:
|
||||
msignals.display(m18n.n('firewall_rules_cmd_failed'), 'warning')
|
||||
logger.warning(m18n.n('firewall_rules_cmd_failed'))
|
||||
else:
|
||||
msignals.display(m18n.n('firewall_reloaded'), 'success')
|
||||
logger.success(m18n.n('firewall_reloaded'))
|
||||
return firewall_list()
|
||||
|
||||
|
||||
|
@ -306,7 +304,7 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
|
||||
# Compatibility with previous version
|
||||
if action == 'reload':
|
||||
logger.warning("'reload' action is deprecated and will be removed")
|
||||
logger.info("'reload' action is deprecated and will be removed")
|
||||
try:
|
||||
# Remove old cron job
|
||||
os.remove('/etc/cron.d/yunohost-firewall')
|
||||
|
@ -349,14 +347,14 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
nb_dev = upnpc.discover()
|
||||
logger.debug('found %d UPnP device(s)', int(nb_dev))
|
||||
if nb_dev < 1:
|
||||
msignals.display(m18n.n('upnp_dev_not_found'), 'error')
|
||||
logger.error(m18n.n('upnp_dev_not_found'))
|
||||
enabled = False
|
||||
else:
|
||||
try:
|
||||
# Select UPnP device
|
||||
upnpc.selectigd()
|
||||
except:
|
||||
logger.exception('unable to select UPnP device')
|
||||
logger.info('unable to select UPnP device', exc_info=1)
|
||||
enabled = False
|
||||
else:
|
||||
# Iterate over ports
|
||||
|
@ -374,8 +372,8 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
upnpc.addportmapping(port, protocol, upnpc.lanaddr,
|
||||
port, 'yunohost firewall: port %d' % port, '')
|
||||
except:
|
||||
logger.exception('unable to add port %d using UPnP',
|
||||
port)
|
||||
logger.info('unable to add port %d using UPnP',
|
||||
port, exc_info=1)
|
||||
enabled = False
|
||||
|
||||
if enabled != firewall['uPnP']['enabled']:
|
||||
|
@ -390,9 +388,9 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
if not no_refresh:
|
||||
# Display success message if needed
|
||||
if action == 'enable' and enabled:
|
||||
msignals.display(m18n.n('upnp_enabled'), 'success')
|
||||
logger.success(m18n.n('upnp_enabled'))
|
||||
elif action == 'disable' and not enabled:
|
||||
msignals.display(m18n.n('upnp_disabled'), 'success')
|
||||
logger.success(m18n.n('upnp_disabled'))
|
||||
# Make sure to disable UPnP
|
||||
elif action != 'disable' and not enabled:
|
||||
firewall_upnp('disable', no_refresh=True)
|
||||
|
@ -455,6 +453,6 @@ def _update_firewall_file(rules):
|
|||
def _on_rule_command_error(returncode, cmd, output):
|
||||
"""Callback for rules commands error"""
|
||||
# Log error and continue commands execution
|
||||
logger.error('"%s" returned non-zero exit status %d:\n%s',
|
||||
cmd, returncode, prependlines(output.rstrip(), '> '))
|
||||
logger.info('"%s" returned non-zero exit status %d:\n%s',
|
||||
cmd, returncode, prependlines(output.rstrip(), '> '))
|
||||
return True
|
||||
|
|
|
@ -32,12 +32,12 @@ import subprocess
|
|||
from glob import iglob
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils import log
|
||||
|
||||
hook_folder = '/usr/share/yunohost/hooks/'
|
||||
custom_hook_folder = '/etc/yunohost/hooks.d/'
|
||||
|
||||
logger = getActionLogger('yunohost.hook')
|
||||
logger = log.getActionLogger('yunohost.hook')
|
||||
|
||||
|
||||
def hook_add(app, file):
|
||||
|
@ -264,14 +264,9 @@ def hook_callback(action, hooks=[], args=None):
|
|||
state = 'succeed'
|
||||
filename = '%s-%s' % (priority, name)
|
||||
try:
|
||||
ret = hook_exec(info['path'], args=args)
|
||||
except:
|
||||
logger.exception("error while executing hook '%s'",
|
||||
info['path'])
|
||||
state = 'failed'
|
||||
if ret != 0:
|
||||
logger.error("error while executing hook '%s', retcode: %d",
|
||||
info['path'], ret)
|
||||
hook_exec(info['path'], args=args, raise_on_error=True)
|
||||
except MoulinetteError as e:
|
||||
logger.error(e.strerror)
|
||||
state = 'failed'
|
||||
try:
|
||||
result[state][name].append(info['path'])
|
||||
|
@ -301,23 +296,31 @@ def hook_check(file):
|
|||
return {}
|
||||
|
||||
|
||||
def hook_exec(file, args=None, raise_on_error=False):
|
||||
def hook_exec(path, args=None, raise_on_error=False, no_trace=False):
|
||||
"""
|
||||
Execute hook from a file with arguments
|
||||
|
||||
Keyword argument:
|
||||
file -- Script to execute
|
||||
path -- Path of the script to execute
|
||||
args -- Arguments to pass to the script
|
||||
raise_on_error -- Raise if the script returns a non-zero exit code
|
||||
no_trace -- Do not print each command that will be executed
|
||||
|
||||
"""
|
||||
from moulinette.utils.stream import NonBlockingStreamReader
|
||||
import time
|
||||
from moulinette.utils.stream import start_async_file_reading
|
||||
from yunohost.app import _value_for_locale
|
||||
|
||||
# Validate hook path
|
||||
if path[0] != '/':
|
||||
path = os.path.realpath(path)
|
||||
if not os.path.isfile(path):
|
||||
raise MoulinetteError(errno.EIO, m18n.g('file_not_exist'))
|
||||
|
||||
if isinstance(args, list):
|
||||
arg_list = args
|
||||
else:
|
||||
required_args = hook_check(file)
|
||||
required_args = hook_check(path)
|
||||
if args is None:
|
||||
args = {}
|
||||
|
||||
|
@ -351,42 +354,60 @@ def hook_exec(file, args=None, raise_on_error=False):
|
|||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('hook_argument_missing', arg['name']))
|
||||
|
||||
file_path = "./"
|
||||
if "/" in file and file[0:2] != file_path:
|
||||
file_path = os.path.dirname(file)
|
||||
file = file.replace(file_path +"/", "")
|
||||
# Construct command variables
|
||||
cmd_fdir, cmd_fname = os.path.split(path)
|
||||
cmd_fname = './{0}'.format(cmd_fname)
|
||||
|
||||
#TODO: Allow python script
|
||||
|
||||
arg_str = ''
|
||||
cmd_args = ''
|
||||
if arg_list:
|
||||
# Concatenate arguments and escape them with double quotes to prevent
|
||||
# bash related issue if an argument is empty and is not the last
|
||||
arg_str = '"{:s}"'.format('" "'.join(str(s) for s in arg_list))
|
||||
cmd_args = '"{:s}"'.format('" "'.join(str(s) for s in arg_list))
|
||||
|
||||
msignals.display(m18n.n('executing_script'))
|
||||
# Construct command to execute
|
||||
command = ['sudo', '-u', 'admin', '-H', 'sh', '-c']
|
||||
if no_trace:
|
||||
cmd = 'cd "{0:s}" && /bin/bash "{1:s}" {2:s}'
|
||||
else:
|
||||
# use xtrace on fd 7 which is redirected to stdout
|
||||
cmd = 'cd "{0:s}" && BASH_XTRACEFD=7 /bin/bash -x "{1:s}" {2:s} 7>&1'
|
||||
command.append(cmd.format(cmd_fdir, cmd_fname, cmd_args))
|
||||
|
||||
p = subprocess.Popen(
|
||||
['sudo', '-u', 'admin', '-H', 'sh', '-c', 'cd "{:s}" && ' \
|
||||
'/bin/bash -x "{:s}" {:s}'.format(
|
||||
file_path, file, arg_str)],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
if logger.isEnabledFor(log.DEBUG):
|
||||
logger.info(m18n.n('executing_command', command=' '.join(command)))
|
||||
else:
|
||||
logger.info(m18n.n('executing_script', script='{0}/{1}'.format(
|
||||
cmd_fdir, cmd_fname)))
|
||||
|
||||
process = subprocess.Popen(command,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
shell=False)
|
||||
|
||||
# Wrap and get process ouput
|
||||
stream = NonBlockingStreamReader(p.stdout)
|
||||
while True:
|
||||
line = stream.readline(True, 0.1)
|
||||
if not line:
|
||||
# Check if process has terminated
|
||||
returncode = p.poll()
|
||||
if returncode is not None:
|
||||
break
|
||||
else:
|
||||
msignals.display(line.rstrip(), 'log')
|
||||
stream.close()
|
||||
# Wrap and get process outputs
|
||||
stdout_reader, stdout_queue = start_async_file_reading(process.stdout)
|
||||
stderr_reader, stderr_queue = start_async_file_reading(process.stderr)
|
||||
while not stdout_reader.eof() or not stderr_reader.eof():
|
||||
while not stdout_queue.empty():
|
||||
line = stdout_queue.get()
|
||||
logger.info(line.rstrip())
|
||||
while not stderr_queue.empty():
|
||||
line = stderr_queue.get()
|
||||
logger.warning(line.rstrip())
|
||||
time.sleep(.1)
|
||||
|
||||
if raise_on_error and returncode != 0:
|
||||
# Terminate outputs readers
|
||||
stdout_reader.join()
|
||||
stderr_reader.join()
|
||||
|
||||
# Get and return process' return code
|
||||
returncode = process.poll()
|
||||
if returncode is None:
|
||||
if raise_on_error:
|
||||
raise MoulinetteError(m18n.n('hook_exec_not_terminated'))
|
||||
else:
|
||||
logger.error(m18n.n('hook_exec_not_terminated'))
|
||||
return 1
|
||||
elif raise_on_error and returncode != 0:
|
||||
raise MoulinetteError(m18n.n('hook_exec_failed'))
|
||||
return returncode
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import difflib
|
|||
import hashlib
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils import log
|
||||
|
||||
template_dir = os.getenv(
|
||||
'YUNOHOST_TEMPLATE_DIR',
|
||||
|
@ -44,6 +45,9 @@ conf_backup_dir = os.getenv(
|
|||
'/home/yunohost.backup/conffiles'
|
||||
)
|
||||
|
||||
logger = log.getActionLogger('yunohost.service')
|
||||
|
||||
|
||||
def service_add(name, status=None, log=None, runlevel=None):
|
||||
"""
|
||||
Add a custom service
|
||||
|
@ -73,7 +77,7 @@ def service_add(name, status=None, log=None, runlevel=None):
|
|||
except:
|
||||
raise MoulinetteError(errno.EIO, m18n.n('service_add_failed', name))
|
||||
|
||||
msignals.display(m18n.n('service_added'), 'success')
|
||||
logger.success(m18n.n('service_added'))
|
||||
|
||||
|
||||
def service_remove(name):
|
||||
|
@ -96,7 +100,7 @@ def service_remove(name):
|
|||
except:
|
||||
raise MoulinetteError(errno.EIO, m18n.n('service_remove_failed', name))
|
||||
|
||||
msignals.display(m18n.n('service_removed'), 'success')
|
||||
logger.success(m18n.n('service_removed'))
|
||||
|
||||
|
||||
def service_start(names):
|
||||
|
@ -111,12 +115,12 @@ def service_start(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('start', name):
|
||||
msignals.display(m18n.n('service_started', name), 'success')
|
||||
logger.success(m18n.n('service_started', name))
|
||||
else:
|
||||
if service_status(name)['status'] != 'running':
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_start_failed', name))
|
||||
msignals.display(m18n.n('service_already_started', name))
|
||||
logger.info(m18n.n('service_already_started', name))
|
||||
|
||||
|
||||
def service_stop(names):
|
||||
|
@ -131,12 +135,12 @@ def service_stop(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('stop', name):
|
||||
msignals.display(m18n.n('service_stopped', name), 'success')
|
||||
logger.success(m18n.n('service_stopped', name))
|
||||
else:
|
||||
if service_status(name)['status'] != 'inactive':
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_stop_failed', name))
|
||||
msignals.display(m18n.n('service_already_stopped', name))
|
||||
logger.info(m18n.n('service_already_stopped', name))
|
||||
|
||||
|
||||
def service_enable(names):
|
||||
|
@ -151,7 +155,7 @@ def service_enable(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('enable', name):
|
||||
msignals.display(m18n.n('service_enabled', name), 'success')
|
||||
logger.success(m18n.n('service_enabled', name))
|
||||
else:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_enable_failed', name))
|
||||
|
@ -169,7 +173,7 @@ def service_disable(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('disable', name):
|
||||
msignals.display(m18n.n('service_disabled', name), 'success')
|
||||
logger.success(m18n.n('service_disabled', name))
|
||||
else:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_disable_failed', name))
|
||||
|
@ -217,8 +221,7 @@ def service_status(names=[]):
|
|||
shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if 'usage:' in e.output.lower():
|
||||
msignals.display(m18n.n('service_status_failed', name),
|
||||
'warning')
|
||||
logger.warning(m18n.n('service_status_failed', name))
|
||||
else:
|
||||
result[name]['status'] = 'inactive'
|
||||
else:
|
||||
|
@ -288,7 +291,7 @@ def service_regenconf(service=None, force=False):
|
|||
hook_callback('conf_regen', [service], args=[force])
|
||||
else:
|
||||
hook_callback('conf_regen', args=[force])
|
||||
msignals.display(m18n.n('services_configured'), 'success')
|
||||
logger.success(m18n.n('services_configured'))
|
||||
|
||||
|
||||
def _run_service_command(action, service):
|
||||
|
@ -317,8 +320,7 @@ def _run_service_command(action, service):
|
|||
ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
# TODO: Log output?
|
||||
msignals.display(m18n.n('service_cmd_exec_failed', ' '.join(e.cmd)),
|
||||
'warning')
|
||||
logger.warning(m18n.n('service_cmd_exec_failed', ' '.join(e.cmd)))
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -467,12 +469,9 @@ def service_saferemove(service, conf_file, force=False):
|
|||
else:
|
||||
services[service]['conffiles'][conf_file] = previous_hash
|
||||
os.remove(conf_backup_file)
|
||||
if os.isatty(1) and \
|
||||
(len(previous_hash) == 32 or previous_hash[-32:] != current_hash):
|
||||
msignals.display(
|
||||
m18n.n('service_configuration_conflict', file=conf_file),
|
||||
'warning'
|
||||
)
|
||||
if len(previous_hash) == 32 or previous_hash[-32:] != current_hash:
|
||||
logger.warning(m18n.n('service_configuration_conflict',
|
||||
file=conf_file))
|
||||
|
||||
_save_services(services)
|
||||
|
||||
|
@ -509,8 +508,7 @@ def service_safecopy(service, new_conf_file, conf_file, force=False):
|
|||
)
|
||||
process.wait()
|
||||
else:
|
||||
msignals.display(m18n.n('service_add_configuration', file=conf_file),
|
||||
'info')
|
||||
logger.info(m18n.n('service_add_configuration', file=conf_file))
|
||||
|
||||
# Add the service if it does not exist
|
||||
if service not in services.keys():
|
||||
|
@ -539,15 +537,9 @@ def service_safecopy(service, new_conf_file, conf_file, force=False):
|
|||
else:
|
||||
new_hash = previous_hash
|
||||
if (len(previous_hash) == 32 or previous_hash[-32:] != current_hash):
|
||||
msignals.display(
|
||||
m18n.n('service_configuration_conflict', file=conf_file),
|
||||
'warning'
|
||||
)
|
||||
print('\n' + conf_file)
|
||||
for line in diff:
|
||||
print(line.strip())
|
||||
print('')
|
||||
|
||||
logger.warning(m18n.n('service_configuration_conflict',
|
||||
file=conf_file, diff=''.join(diff)))
|
||||
|
||||
# Remove the backup file if the configuration has not changed
|
||||
if new_hash == previous_hash:
|
||||
try:
|
||||
|
|
Loading…
Add table
Reference in a new issue