diff --git a/.gitignore b/.gitignore index c1061c4838c..1ced025a0e6 100644 --- a/.gitignore +++ b/.gitignore @@ -149,4 +149,4 @@ prof/ # outputs from make .stack-*.yml -services/catalog/log.txt +services/web/server/tests/data/static/resource/statics.json diff --git a/services/web/server/src/simcore_service_webserver/statics.py b/services/web/server/src/simcore_service_webserver/statics.py index 85dfc53383c..96b0e01093c 100644 --- a/services/web/server/src/simcore_service_webserver/statics.py +++ b/services/web/server/src/simcore_service_webserver/statics.py @@ -9,7 +9,10 @@ import json import logging import os +import shutil +import tempfile from pathlib import Path +from typing import Set from aiohttp import web @@ -17,24 +20,30 @@ from servicelib.application_setup import ModuleCategory, app_module_setup INDEX_RESOURCE_NAME = "statics.index" +TMPDIR_KEY = f"{__name__}.tmpdir" log = logging.getLogger(__file__) def get_client_outdir(app: web.Application) -> Path: cfg = app[APP_CONFIG_KEY]["main"] - - # pylint 2.3.0 produces 'E1101: Instance of 'Path' has no 'expanduser' member (no-member)' ONLY - # with the installed code and not with the development code! - client_dir = Path(cfg["client_outdir"]).expanduser() # pylint: disable=E1101 + client_dir = Path(cfg["client_outdir"]).expanduser() if not client_dir.exists(): - txt = reason = "Front-end application is not available" - if cfg["testing"]: - reason = "Invalid client source path: %s" % client_dir - raise web.HTTPServiceUnavailable(reason=reason, text=txt) + tmp_dir = tempfile.mkdtemp(suffix="client_outdir") + log.error( + "Invalid client source path [%s]. Defaulting to %s", client_dir, tmp_dir + ) + client_dir = tmp_dir + app[TMPDIR_KEY] = tmp_dir return client_dir +async def _delete_tmps(app: web.Application): + tmp_dir = app.get(TMPDIR_KEY) + if tmp_dir: + shutil.rmtree(tmp_dir, ignore_errors=True) + + async def index(request: web.Request): """ Serves boot application under index @@ -46,46 +55,47 @@ async def index(request: web.Request): return web.Response(text=ofh.read(), content_type="text/html") -def write_statics_file(directory): +def write_statics_file(directory: Path) -> None: + # ensures directory exists + os.makedirs(directory, exist_ok=True) + + # create statis fiel statics = {} statics["stackName"] = os.environ.get("SWARM_STACK_NAME") statics["buildDate"] = os.environ.get("BUILD_DATE") - with open(directory / "statics.json", "w") as statics_file: - json.dump(statics, statics_file) + with open(directory / "statics.json", "wt") as fh: + json.dump(statics, fh) @app_module_setup(__name__, ModuleCategory.SYSTEM, logger=log) def setup_statics(app: web.Application): - # TODO: Should serving front-end ria be configurable? - # Front-end Rich Interface Application (RIA) - try: - outdir = get_client_outdir(app) - - # Checks integrity of RIA source before serving - EXPECTED_FOLDERS = ("osparc", "resource", "transpiled") - folders = [x for x in outdir.iterdir() if x.is_dir()] - - for name in EXPECTED_FOLDERS: - folder_names = [path.name for path in folders] - if name not in folder_names: - raise web.HTTPServiceUnavailable( - reason="Invalid front-end source-output folders" - " Expected %s, got %s in %s" - % (EXPECTED_FOLDERS, folder_names, outdir), - text="Front-end application is not available", - ) - - # TODO: map ui to /ui or create an alias!? - app.router.add_get("/", index, name=INDEX_RESOURCE_NAME) - - # NOTE: source-output and build-output have both the same subfolder structure - # TODO: check whether this can be done at oncen - for path in folders: - app.router.add_static("/" + path.name, path) - - # Create statics file - write_statics_file(outdir / "resource") - - except web.HTTPServiceUnavailable as ex: - log.exception(ex.text) - return + # Serves Front-end Rich Interface Application (RIA) + app.router.add_get("/", index, name=INDEX_RESOURCE_NAME) + + # NOTE: source-output and build-output have both the same subfolder structure + outdir: Path = get_client_outdir(app) + + # Create statics file + write_statics_file(outdir / "resource") + + required_dirs = ["osparc", "resource", "transpiled"] + folders = [x for x in outdir.iterdir() if x.is_dir()] + + # Checks integrity of RIA source before serving and warn! + for name in required_dirs: + folder_names = [path.name for path in folders] + if name not in folder_names: + log.warning( + "Missing folders: expected %s, got %s in %s", + required_dirs, + folder_names, + outdir, + ) + + # Add static routes + folders: Set[Path] = set(folders).union([outdir / name for name in required_dirs]) + for path in folders: + app.router.add_static("/" + path.name, path) + + # cleanup + app.on_cleanup.append(_delete_tmps)