diff --git a/backend/app.py b/backend/app.py index 896f1035fd748df697776c66690ada75f16bd637..5594bb3c58f2ce1b09f3fae4b97852390cf730c9 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,49 @@ 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` has to run only once per dashboard instance, or at least not +# concurrently. We used to have locking in place to prevent concurrent +# executions of this code, but we now rely on proper configuration of gunicorn +# and/or flask to make sure this is run only once. In particular: +# * we have "preload" on for gunicorn, so this file is loaded only once, before +# workers are forked; +# * 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 DEV_MODE: + 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).") +else: + logging.info("Running initialization code (production mode).") + init_routines() app.register_blueprint(api_v1) app.register_blueprint(web) diff --git a/backend/config.py b/backend/config.py index 6e5d37ac1221925e8403612674225bd0ccf5314d..b2404b9410e51e2550df76c921533d11d0102526 100644 --- a/backend/config.py +++ b/backend/config.py @@ -21,5 +21,9 @@ 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" +# We use this to detect whether we run in production mode (gunicorn, as +# specified in the docker image) or dev mode (flask run, as specified in docker +# compose config). +DEV_MODE = os.environ.get("DASHBOARD_DEV_MODE", "False").lower() in ('true', '1') DEMO_INSTANCE = os.environ.get("DASHBOARD_DEMO_INSTANCE", "False").lower() in ('true', '1') diff --git a/docker-compose.yml b/docker-compose.yml index 00b2d76d4e3f7f2cf6a5157b97db69ccbd06ef18..be4376c2371883520d79726061d11d7fa816a909 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,7 +54,7 @@ services: - "$KUBECONFIG:/.kube/config" depends_on: - kube_port_mysql - entrypoint: ["bash", "-c", "flask run --host $$(hostname -i)"] + entrypoint: ["bash", "-c", "DASHBOARD_DEV_MODE=true flask run --host $$(hostname -i)"] kube_port_kratos_admin: image: bitnami/kubectl:1.27.2 user: "${KUBECTL_UID}:${KUBECTL_GID}"