182 lines
5.7 KiB
Python
182 lines
5.7 KiB
Python
import os
|
|
import sys
|
|
import atexit
|
|
import signal
|
|
import logging
|
|
|
|
from pprint import pformat
|
|
|
|
from logging import NullHandler
|
|
|
|
import click
|
|
from tornado.options import options
|
|
from tornado.options import parse_command_line, parse_config_file
|
|
from tornado.log import enable_pretty_logging
|
|
from celery.bin.base import CeleryCommand
|
|
|
|
from .app import Flower
|
|
from .urls import settings
|
|
from .utils import abs_path, prepend_url, strtobool
|
|
from .options import DEFAULT_CONFIG_FILE, default_options
|
|
from .views.auth import validate_auth_option
|
|
|
|
logger = logging.getLogger(__name__)
|
|
ENV_VAR_PREFIX = 'FLOWER_'
|
|
|
|
|
|
def sigterm_handler(signum, _):
|
|
logger.info('%s detected, shutting down', signum)
|
|
sys.exit(0)
|
|
|
|
|
|
@click.command(cls=CeleryCommand,
|
|
context_settings={
|
|
'ignore_unknown_options': True
|
|
})
|
|
@click.argument("tornado_argv", nargs=-1, type=click.UNPROCESSED)
|
|
@click.pass_context
|
|
def flower(ctx, tornado_argv):
|
|
"""Web based tool for monitoring and administrating Celery clusters."""
|
|
warn_about_celery_args_used_in_flower_command(ctx, tornado_argv)
|
|
apply_env_options()
|
|
apply_options(sys.argv[0], tornado_argv)
|
|
|
|
extract_settings()
|
|
setup_logging()
|
|
|
|
app = ctx.obj.app
|
|
flower_app = Flower(capp=app, options=options, **settings)
|
|
|
|
atexit.register(flower_app.stop)
|
|
signal.signal(signal.SIGTERM, sigterm_handler)
|
|
|
|
if not ctx.obj.quiet:
|
|
print_banner(app, 'ssl_options' in settings)
|
|
|
|
try:
|
|
flower_app.start()
|
|
except (KeyboardInterrupt, SystemExit):
|
|
pass
|
|
|
|
|
|
def apply_env_options():
|
|
"apply options passed through environment variables"
|
|
env_options = filter(is_flower_envvar, os.environ)
|
|
for env_var_name in env_options:
|
|
name = env_var_name.replace(ENV_VAR_PREFIX, '', 1).lower()
|
|
value = os.environ[env_var_name]
|
|
try:
|
|
option = options._options[name] # pylint: disable=protected-access
|
|
except KeyError:
|
|
option = options._options[name.replace('_', '-')] # pylint: disable=protected-access
|
|
if option.multiple:
|
|
value = [option.type(i) for i in value.split(',')]
|
|
else:
|
|
if option.type is bool:
|
|
value = bool(strtobool(value))
|
|
else:
|
|
value = option.type(value)
|
|
setattr(options, name, value)
|
|
|
|
|
|
def apply_options(prog_name, argv):
|
|
"apply options passed through the configuration file"
|
|
argv = list(filter(is_flower_option, argv))
|
|
# parse the command line to get --conf option
|
|
parse_command_line([prog_name] + argv)
|
|
try:
|
|
parse_config_file(os.path.abspath(options.conf), final=False)
|
|
parse_command_line([prog_name] + argv)
|
|
except IOError:
|
|
if os.path.basename(options.conf) != DEFAULT_CONFIG_FILE:
|
|
raise
|
|
|
|
|
|
def warn_about_celery_args_used_in_flower_command(ctx, flower_args):
|
|
celery_options = [option for param in ctx.parent.command.params for option in param.opts]
|
|
|
|
incorrectly_used_args = []
|
|
for arg in flower_args:
|
|
arg_name, _, _ = arg.partition("=")
|
|
if arg_name in celery_options:
|
|
incorrectly_used_args.append(arg_name)
|
|
|
|
if incorrectly_used_args:
|
|
logger.warning(
|
|
'You have incorrectly specified the following celery arguments after flower command:'
|
|
' %s. '
|
|
'Please specify them after celery command instead following this template: '
|
|
'celery [celery args] flower [flower args].', incorrectly_used_args
|
|
)
|
|
|
|
|
|
def setup_logging():
|
|
if options.debug and options.logging == 'info':
|
|
options.logging = 'debug'
|
|
enable_pretty_logging()
|
|
else:
|
|
logging.getLogger("tornado.access").addHandler(NullHandler())
|
|
logging.getLogger("tornado.access").propagate = False
|
|
|
|
|
|
def extract_settings():
|
|
settings['debug'] = options.debug
|
|
|
|
if options.cookie_secret:
|
|
settings['cookie_secret'] = options.cookie_secret
|
|
|
|
if options.url_prefix:
|
|
for name in ['login_url', 'static_url_prefix']:
|
|
settings[name] = prepend_url(settings[name], options.url_prefix)
|
|
|
|
if options.auth:
|
|
settings['oauth'] = {
|
|
'key': options.oauth2_key or os.environ.get('FLOWER_OAUTH2_KEY'),
|
|
'secret': options.oauth2_secret or os.environ.get('FLOWER_OAUTH2_SECRET'),
|
|
'redirect_uri': options.oauth2_redirect_uri or os.environ.get('FLOWER_OAUTH2_REDIRECT_URI'),
|
|
}
|
|
|
|
if options.certfile and options.keyfile:
|
|
settings['ssl_options'] = dict(certfile=abs_path(options.certfile),
|
|
keyfile=abs_path(options.keyfile))
|
|
if options.ca_certs:
|
|
settings['ssl_options']['ca_certs'] = abs_path(options.ca_certs)
|
|
|
|
if options.auth and not validate_auth_option(options.auth):
|
|
logger.error("Invalid '--auth' option: %s", options.auth)
|
|
sys.exit(1)
|
|
|
|
|
|
def is_flower_option(arg):
|
|
name, _, _ = arg.lstrip('-').partition("=")
|
|
name = name.replace('-', '_')
|
|
return hasattr(options, name)
|
|
|
|
|
|
def is_flower_envvar(name):
|
|
return name.startswith(ENV_VAR_PREFIX) and \
|
|
name[len(ENV_VAR_PREFIX):].lower() in default_options
|
|
|
|
|
|
def print_banner(app, ssl):
|
|
if not options.unix_socket:
|
|
if options.url_prefix:
|
|
prefix_str = f'/{options.url_prefix}/'
|
|
else:
|
|
prefix_str = ''
|
|
|
|
logger.info(
|
|
"Visit me at http%s://%s:%s%s", 's' if ssl else '',
|
|
options.address or '0.0.0.0', options.port,
|
|
prefix_str
|
|
)
|
|
else:
|
|
logger.info("Visit me via unix socket file: %s", options.unix_socket)
|
|
|
|
logger.info('Broker: %s', app.connection().as_uri())
|
|
logger.info(
|
|
'Registered tasks: \n%s',
|
|
pformat(sorted(app.tasks.keys()))
|
|
)
|
|
logger.debug('Settings: %s', pformat(settings))
|