from flask import Flask, jsonify from flask_cors import CORS from flask_jwt_extended import JWTManager import flask_migrate from jsonschema.exceptions import ValidationError from NamedAtomicLock import NamedAtomicLock from werkzeug.exceptions import BadRequest # These imports are required from areas import api_v1 from cliapp import cli from web import web from areas import users from areas import apps from areas import auth from areas import roles from cliapp import cliapp from web import login from database import db from helpers import ( BadRequest, KratosError, HydraError, Unauthorized, bad_request_error, validation_error, kratos_error, global_error, hydra_error, unauthorized_error, ) import cluster_config from config import * import logging import migration_reset import sys # Configure logging. from logging.config import dictConfig dictConfig({ 'version': 1, 'formatters': {'default': { 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', }}, 'handlers': {'wsgi': { 'class': 'logging.StreamHandler', 'stream': 'ext://flask.logging.wsgi_errors_stream', 'formatter': 'default', }}, 'root': { 'level': 'INFO', 'handlers': ['wsgi'], } }) app = Flask(__name__) app.config["SECRET_KEY"] = SECRET_KEY app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = SQLALCHEMY_TRACK_MODIFICATIONS app.logger.setLevel(logging.INFO) app.logger.info("Starting dashboard backend.") 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(): 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() app.register_blueprint(api_v1) app.register_blueprint(web) app.register_blueprint(cli) # Error handlers app.register_error_handler(Exception, global_error) app.register_error_handler(BadRequest, bad_request_error) app.register_error_handler(ValidationError, validation_error) app.register_error_handler(KratosError, kratos_error) app.register_error_handler(HydraError, hydra_error) app.register_error_handler(Unauthorized, unauthorized_error) jwt = JWTManager(app) # When token is not valid or missing handler @jwt.invalid_token_loader @jwt.unauthorized_loader @jwt.expired_token_loader def expired_token_callback(*args): return jsonify({"errorMessage": "Unauthorized"}), 401 @app.route("/") def index(): return "Stackspin API v1.0"