diff --git a/backend/Dockerfile b/backend/Dockerfile index 90f087405cedb845074e0b05b274d0c500b75fa0..c5c9434d3c7c82694960a80267e61a4432213341 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -20,8 +20,7 @@ RUN pip install --no-cache-dir -r requirements.txt # now copy all the files in this directory to /app COPY . . -# Listen to port 80 at runtime EXPOSE 5000 # Define our command to be run when launching the container -CMD ["gunicorn", "app:app", "-b", "0.0.0.0:5000", "--workers", "8", "--reload", "--capture-output", "--enable-stdio-inheritance", "--log-level", "DEBUG"] +CMD ["gunicorn", "app:app", "-b", "0.0.0.0:5000", "--workers", "8", "--preload", "--capture-output", "--enable-stdio-inheritance", "--log-level", "DEBUG"] diff --git a/backend/app.py b/backend/app.py index 896f1035fd748df697776c66690ada75f16bd637..129e935366b5440be7f24d2ba3e9c51c2b3e9e14 100644 --- a/backend/app.py +++ b/backend/app.py @@ -37,6 +37,7 @@ import cluster_config from config import * import logging import migration_reset +import os import sys # Configure logging. @@ -69,35 +70,47 @@ cors = CORS(app) db.init_app(app) -# We'll now perform some initialization routines. Because these have to be done -# once at startup, not for every gunicorn worker, we take a machine-wide lock -# for this. -init_lock = NamedAtomicLock('dashboard_init') -if init_lock.acquire(): +def init_routines(): + with app.app_context(): + # We have reset the alembic migration history at Stackspin version 2.2. + # This checks whether we need to prepare the database to follow that + # change. + migration_reset.reset() + flask_migrate.Migrate(app, db) try: with app.app_context(): - # We have reset the alembic migration history at Stackspin version 2.2. - # This checks whether we need to prepare the database to follow that - # change. - migration_reset.reset() - flask_migrate.Migrate(app, db) - try: - with app.app_context(): - flask_migrate.upgrade() - except Exception as e: - app.logger.info(f"upgrade failed: {type(e)}: {e}") - sys.exit(2) - - # We need this app context in order to talk the database, which is managed by - # flask-sqlalchemy, which assumes a flask app context. - with app.app_context(): - # Load the list of apps from a configmap and store any missing ones in the - # database. - cluster_config.populate_apps() - # Same for the list of oauthclients. - cluster_config.populate_oauthclients() - finally: - init_lock.release() + flask_migrate.upgrade() + # TODO: actually flask_migrate.upgrade will catch any errors and + # exit the program :/ + except Exception as e: + app.logger.info(f"upgrade failed: {type(e)}: {e}") + sys.exit(2) + + # We need this app context in order to talk the database, which is managed by + # flask-sqlalchemy, which assumes a flask app context. + with app.app_context(): + # Load the list of apps from a configmap and store any missing ones in the + # database. + cluster_config.populate_apps() + # Same for the list of oauthclients. + cluster_config.populate_oauthclients() + +# `init_routines`should only run once per dashboard instance. To enforce this we +# have different behaviour for production and development mode: +# * we have "preload" on for gunicorn, so this file is loaded only once, before +# workers are forked (production); +# * we make sure that in development mode we run this only once, even though +# this file is loaded twice by flask for some reason. +if RUN_BY_GUNICORN: + logging.info("Running initialization code (production mode).") + init_routines() +else: + logging.info("WERKZEUG_RUN_MAIN: {}".format(os.environ.get("WERKZEUG_RUN_MAIN", "unset"))) + if os.environ.get("WERKZEUG_RUN_MAIN") == "true": + logging.info("Running initialization code (dev mode).") + init_routines() + else: + logging.info("Not running initialization code (dev mode).") app.register_blueprint(api_v1) app.register_blueprint(web) diff --git a/backend/config.py b/backend/config.py index 6e5d37ac1221925e8403612674225bd0ccf5314d..04039e5adbf896b51b5a5958672c48ea229776ce 100644 --- a/backend/config.py +++ b/backend/config.py @@ -21,5 +21,6 @@ SQLALCHEMY_TRACK_MODIFICATIONS = False # running in a Kubernetes pod. Set it to "false" to load the config from the # `KUBECONFIG` environment variable. LOAD_INCLUSTER_CONFIG = os.environ.get("LOAD_INCLUSTER_CONFIG").lower() == "true" +RUN_BY_GUNICORN = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "") DEMO_INSTANCE = os.environ.get("DASHBOARD_DEMO_INSTANCE", "False").lower() in ('true', '1')