2021-11-03 11:21:39 -04:00
|
|
|
import logging.config
|
2019-12-18 14:02:17 -05:00
|
|
|
import os
|
2022-02-18 17:20:04 -05:00
|
|
|
import traceback
|
2021-10-19 10:13:43 -04:00
|
|
|
|
|
|
|
import click
|
2020-06-01 19:59:55 -06:00
|
|
|
import sentry_sdk
|
2019-12-18 14:02:17 -05:00
|
|
|
|
|
|
|
import connexion
|
2021-10-19 10:13:43 -04:00
|
|
|
from SpiffWorkflow import WorkflowException
|
2021-11-09 12:55:06 -05:00
|
|
|
from SpiffWorkflow.exceptions import WorkflowTaskExecException
|
2020-08-27 14:00:14 -04:00
|
|
|
from connexion import ProblemException
|
|
|
|
from flask import Response
|
2019-12-19 11:58:51 -05:00
|
|
|
from flask_cors import CORS
|
2019-12-18 14:02:17 -05:00
|
|
|
from flask_marshmallow import Marshmallow
|
2020-06-04 20:37:28 -06:00
|
|
|
from flask_mail import Mail
|
2019-12-18 14:02:17 -05:00
|
|
|
from flask_migrate import Migrate
|
2020-05-12 22:42:02 -04:00
|
|
|
from flask_sqlalchemy import SQLAlchemy
|
2020-06-01 19:59:55 -06:00
|
|
|
from sentry_sdk.integrations.flask import FlaskIntegration
|
2021-06-09 09:24:37 -04:00
|
|
|
from apscheduler.schedulers.background import BackgroundScheduler
|
2021-10-01 15:35:22 -04:00
|
|
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
2021-06-09 10:42:34 -04:00
|
|
|
|
2022-02-04 14:50:31 -05:00
|
|
|
|
2019-12-18 14:02:17 -05:00
|
|
|
connexion_app = connexion.FlaskApp(__name__)
|
|
|
|
|
|
|
|
app = connexion_app.app
|
2021-10-01 15:35:22 -04:00
|
|
|
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1) # respect the X-Forwarded-Proto if behind a proxy.
|
2019-12-18 14:02:17 -05:00
|
|
|
app.config.from_object('config.default')
|
2020-02-18 16:38:56 -05:00
|
|
|
|
2019-12-18 14:02:17 -05:00
|
|
|
if "TESTING" in os.environ and os.environ["TESTING"] == "true":
|
|
|
|
app.config.from_object('config.testing')
|
2020-02-28 11:33:08 -05:00
|
|
|
app.config.from_pyfile('../config/testing.py')
|
2021-11-10 17:43:34 -05:00
|
|
|
import logging
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
2020-02-05 13:43:59 -05:00
|
|
|
else:
|
2020-02-06 14:36:02 -05:00
|
|
|
app.config.root_path = app.instance_path
|
2020-02-05 13:43:59 -05:00
|
|
|
app.config.from_pyfile('config.py', silent=True)
|
2021-11-10 17:43:34 -05:00
|
|
|
from config.logging import logging_config
|
|
|
|
logging.config.dictConfig(logging_config)
|
2019-12-18 14:02:17 -05:00
|
|
|
|
2020-11-19 11:25:58 -05:00
|
|
|
|
2019-12-18 14:02:17 -05:00
|
|
|
db = SQLAlchemy(app)
|
2020-01-21 15:21:33 -05:00
|
|
|
""":type: sqlalchemy.orm.SQLAlchemy"""
|
2020-01-14 11:45:12 -05:00
|
|
|
|
|
|
|
session = db.session
|
2020-01-21 15:21:33 -05:00
|
|
|
""":type: sqlalchemy.orm.Session"""
|
2021-06-09 09:24:37 -04:00
|
|
|
scheduler = BackgroundScheduler()
|
2020-01-14 11:45:12 -05:00
|
|
|
|
2022-02-04 14:50:31 -05:00
|
|
|
|
2020-06-25 16:18:42 -06:00
|
|
|
# Mail settings
|
|
|
|
mail = Mail(app)
|
|
|
|
|
2019-12-18 14:02:17 -05:00
|
|
|
migrate = Migrate(app, db)
|
|
|
|
ma = Marshmallow(app)
|
|
|
|
|
|
|
|
from crc import models
|
2020-01-14 15:26:39 -05:00
|
|
|
from crc import api
|
2020-06-16 12:26:25 -04:00
|
|
|
from crc.api import admin
|
2021-06-09 10:42:34 -04:00
|
|
|
from crc.services.workflow_service import WorkflowService
|
2020-05-24 00:05:13 -04:00
|
|
|
connexion_app.add_api('api.yml', base_path='/v1.0')
|
2020-05-12 12:23:47 -04:00
|
|
|
|
2021-12-09 08:54:44 -05:00
|
|
|
# needed function to avoid circular import
|
2021-06-09 10:42:34 -04:00
|
|
|
def process_waiting_tasks():
|
|
|
|
with app.app_context():
|
|
|
|
WorkflowService.do_waiting()
|
|
|
|
|
2021-12-09 08:54:44 -05:00
|
|
|
|
|
|
|
@app.before_first_request
|
|
|
|
def init_scheduler():
|
2022-02-15 11:06:11 -05:00
|
|
|
if app.config['PROCESS_WAITING_TASKS']:
|
|
|
|
scheduler.add_job(process_waiting_tasks, 'interval', minutes=1)
|
2022-02-17 12:12:42 -05:00
|
|
|
scheduler.add_job(WorkflowService().process_erroring_workflows, 'interval', minutes=1440)
|
2022-02-15 11:06:11 -05:00
|
|
|
scheduler.start()
|
2021-06-09 10:42:34 -04:00
|
|
|
|
2020-06-08 07:14:31 -06:00
|
|
|
|
2020-05-12 12:23:47 -04:00
|
|
|
# Convert list of allowed origins to list of regexes
|
2020-05-12 14:27:17 -04:00
|
|
|
origins_re = [r"^https?:\/\/%s(.*)" % o.replace('.', '\.') for o in app.config['CORS_ALLOW_ORIGINS']]
|
2020-05-16 11:36:48 -04:00
|
|
|
cors = CORS(connexion_app.app, origins=origins_re)
|
2019-12-18 14:02:17 -05:00
|
|
|
|
2020-06-08 07:14:31 -06:00
|
|
|
# Sentry error handling
|
2020-07-19 16:40:33 -06:00
|
|
|
if app.config['SENTRY_ENVIRONMENT']:
|
2020-06-01 19:59:55 -06:00
|
|
|
sentry_sdk.init(
|
2020-07-19 16:40:33 -06:00
|
|
|
environment=app.config['SENTRY_ENVIRONMENT'],
|
2020-06-01 19:59:55 -06:00
|
|
|
dsn="https://25342ca4e2d443c6a5c49707d68e9f40@o401361.ingest.sentry.io/5260915",
|
|
|
|
integrations=[FlaskIntegration()]
|
|
|
|
)
|
|
|
|
|
2020-08-27 14:00:14 -04:00
|
|
|
|
|
|
|
# Connexion Error handling
|
|
|
|
def render_errors(exception):
|
|
|
|
from crc.api.common import ApiError, ApiErrorSchema
|
2021-03-08 14:00:03 -05:00
|
|
|
error = ApiError(code=exception.title, message=exception.detail, status_code=exception.status)
|
2022-02-08 11:30:13 -05:00
|
|
|
return Response(ApiErrorSchema().dumps(error), status=500, mimetype="text/json")
|
2020-08-27 14:00:14 -04:00
|
|
|
|
|
|
|
|
|
|
|
connexion_app.add_error_handler(ProblemException, render_errors)
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-05-26 22:42:49 -04:00
|
|
|
print('=== USING THESE CONFIG SETTINGS: ===')
|
2020-05-31 16:49:39 -04:00
|
|
|
print('APPLICATION_ROOT = ', app.config['APPLICATION_ROOT'])
|
2020-05-26 22:42:49 -04:00
|
|
|
print('CORS_ALLOW_ORIGINS = ', app.config['CORS_ALLOW_ORIGINS'])
|
2020-05-31 16:49:39 -04:00
|
|
|
print('DB_HOST = ', app.config['DB_HOST'])
|
2020-05-26 22:42:49 -04:00
|
|
|
print('LDAP_URL = ', app.config['LDAP_URL'])
|
2020-05-31 16:49:39 -04:00
|
|
|
print('PB_BASE_URL = ', app.config['PB_BASE_URL'])
|
2020-05-26 22:42:49 -04:00
|
|
|
print('PB_ENABLED = ', app.config['PB_ENABLED'])
|
2020-05-31 16:49:39 -04:00
|
|
|
print('PRODUCTION = ', app.config['PRODUCTION'])
|
|
|
|
print('TESTING = ', app.config['TESTING'])
|
|
|
|
print('TEST_UID = ', app.config['TEST_UID'])
|
|
|
|
print('ADMIN_UIDS = ', app.config['ADMIN_UIDS'])
|
2020-02-18 16:38:56 -05:00
|
|
|
|
2021-01-08 13:23:01 -05:00
|
|
|
|
2020-06-05 17:49:55 -04:00
|
|
|
@app.cli.command()
|
|
|
|
def clear_db():
|
|
|
|
"""Load example data into the database."""
|
|
|
|
from example_data import ExampleDataLoader
|
|
|
|
ExampleDataLoader.clean_db()
|
2021-10-19 10:13:43 -04:00
|
|
|
|
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("study_id")
|
2021-11-09 12:55:06 -05:00
|
|
|
@click.argument("category", required=False)
|
|
|
|
@click.argument("spec_id", required=False)
|
2021-10-19 10:13:43 -04:00
|
|
|
def validate_all(study_id, category=None, spec_id=None):
|
|
|
|
"""Step through all the local workflows and validate them, returning any errors. This make take forever.
|
|
|
|
Please provide a real study id to use for validation, an optional category can be specified to only validate
|
|
|
|
that category, and you can further specify a specific spec, if needed."""
|
|
|
|
from crc.services.workflow_service import WorkflowService
|
2022-02-17 12:39:52 -05:00
|
|
|
from crc.services.workflow_spec_service import WorkflowSpecService
|
2021-10-19 10:13:43 -04:00
|
|
|
from crc.api.common import ApiError
|
2021-11-09 12:55:06 -05:00
|
|
|
from crc.models.study import StudyModel
|
|
|
|
from crc.models.user import UserModel
|
|
|
|
from flask import g
|
2021-10-19 10:13:43 -04:00
|
|
|
|
2021-11-09 12:55:06 -05:00
|
|
|
study = session.query(StudyModel).filter(StudyModel.id == study_id).first()
|
|
|
|
g.user = session.query(UserModel).filter(UserModel.uid == study.user_uid).first()
|
|
|
|
g.token = "anything_is_fine_just_need_something."
|
2022-02-18 11:40:58 -05:00
|
|
|
specs = WorkflowSpecService().get_specs()
|
2021-10-19 10:13:43 -04:00
|
|
|
for spec in specs:
|
|
|
|
if spec_id and spec_id != spec.id:
|
|
|
|
continue
|
|
|
|
if category and (not spec.category or spec.category.display_name != category):
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
WorkflowService.test_spec(spec.id, validate_study_id=study_id)
|
|
|
|
except ApiError as e:
|
2021-11-09 12:55:06 -05:00
|
|
|
if e.code == 'disabled_workflow':
|
|
|
|
print(f"Skipping {spec.id} in category {spec.category.display_name}, it is disabled for this study.")
|
|
|
|
else:
|
|
|
|
print(f"API Error {e.code}, validate workflow {spec.id} in Category {spec.category.display_name}")
|
2022-02-18 11:40:58 -05:00
|
|
|
continue
|
2021-11-09 12:55:06 -05:00
|
|
|
except WorkflowTaskExecException as e:
|
|
|
|
print(f"Workflow Error, {e}, in Task {e.task.name} validate workflow {spec.id} in Category {spec.category.display_name}")
|
2022-02-18 11:40:58 -05:00
|
|
|
continue
|
2021-11-09 12:55:06 -05:00
|
|
|
except Exception as e:
|
2022-02-18 11:40:58 -05:00
|
|
|
print(f"Unexpected Error ({e.__class__.__name__}), {e} validate workflow {spec.id} in Category {spec.category.display_name}")
|
2022-02-18 17:20:04 -05:00
|
|
|
# printing stack trace
|
|
|
|
traceback.print_exc()
|
2021-10-19 10:13:43 -04:00
|
|
|
print(e)
|
2022-02-18 17:20:04 -05:00
|
|
|
return
|