diff --git a/services/dynamic-sidecar/Makefile b/services/dynamic-sidecar/Makefile index 234af0650ea..7ecf6c298a6 100644 --- a/services/dynamic-sidecar/Makefile +++ b/services/dynamic-sidecar/Makefile @@ -1,8 +1,6 @@ include ../../scripts/common.Makefile include ../../scripts/common-service.Makefile -APP_NAME := $(notdir $(CURDIR)) - .DEFAULT_GOAL := help @@ -12,10 +10,17 @@ APP_NAME := $(notdir $(CURDIR)) @echo "WARNING ##### $@ does not exist, cloning $< as $@ ############"; cp $< $@) -.PHONY: openapi.json + + +.PHONY: openapi-specs openapi.json +openapi-specs: openapi.json openapi.json: .env ## Creates OAS document openapi.json - # generating openapi specs (OAS) file - export $(shell grep -v '^#' $< | xargs -0) && python3 -c "import json; from $(APP_PACKAGE_NAME).main import *; print( json.dumps(app.openapi(), indent=2) )" > $@ + # generating openapi specs file under $< + @set -o allexport; \ + source .env; \ + set +o allexport; \ + simcore-service-dynamic-sidecar openapi > $@ + # validates OAS file: $@ @cd $(CURDIR); \ $(SCRIPTS_DIR)/openapi-generator-cli.bash validate --input-spec /local/$@ diff --git a/services/dynamic-sidecar/docker/boot.sh b/services/dynamic-sidecar/docker/boot.sh index e10b8764d88..0f752df7d72 100755 --- a/services/dynamic-sidecar/docker/boot.sh +++ b/services/dynamic-sidecar/docker/boot.sh @@ -35,7 +35,7 @@ if [ "${SC_BOOT_MODE}" = "debug-ptvsd" ]; then exec sh -c " cd services/dynamic-sidecar/src/simcore_service_dynamic_sidecar && \ - uvicorn main:app \ + uvicorn main:the_app \ --host 0.0.0.0 \ --reload \ $reload_dir_packages @@ -43,7 +43,7 @@ if [ "${SC_BOOT_MODE}" = "debug-ptvsd" ]; then --log-level \"${SERVER_LOG_LEVEL}\" " else - exec uvicorn simcore_service_dynamic_sidecar.main:app \ + exec uvicorn simcore_service_dynamic_sidecar.main:the_app \ --host 0.0.0.0 \ --log-level "${SERVER_LOG_LEVEL}" diff --git a/services/dynamic-sidecar/scripts/Makefile b/services/dynamic-sidecar/scripts/Makefile index ad01483e49b..665cece87d8 100644 --- a/services/dynamic-sidecar/scripts/Makefile +++ b/services/dynamic-sidecar/scripts/Makefile @@ -15,6 +15,10 @@ push-outputs: ## push the outputs for this service @echo ">>>>> Expect a 204 reply if OK <<<<<" curl -v -X POST ${BASE_ADDRESS}/containers/ports/outputs:push +.PHONY: info +info: ## displays app info + # app settings + @simcore-service-dynamic-sidecar settings --as-json .PHONY: help help: ## this help diff --git a/services/dynamic-sidecar/setup.py b/services/dynamic-sidecar/setup.py index fae95334ff3..c4171130c44 100644 --- a/services/dynamic-sidecar/setup.py +++ b/services/dynamic-sidecar/setup.py @@ -53,6 +53,11 @@ def read_reqs(reqs_path: Path) -> set[str]: PROD_REQUIREMENTS=PROD_REQUIREMENTS, TEST_REQUIREMENTS=TEST_REQUIREMENTS, setup_requires=["setuptools_scm"], + entry_points={ + "console_scripts": [ + "simcore-service-dynamic-sidecar=simcore_service_dynamic_sidecar.cli:main", + ], + }, ) diff --git a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/cli.py b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/cli.py new file mode 100644 index 00000000000..4135c99986a --- /dev/null +++ b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/cli.py @@ -0,0 +1,28 @@ +import json +import logging + +import typer +from settings_library.utils_cli import create_settings_command +from simcore_service_dynamic_sidecar.core.application import create_basic_app + +from ._meta import PROJECT_NAME +from .core.settings import DynamicSidecarSettings + +log = logging.getLogger(__name__) +main = typer.Typer(name=PROJECT_NAME) + + +main.command()(create_settings_command(settings_cls=DynamicSidecarSettings, logger=log)) + + +@main.command() +def openapi(): + """Prints OpenAPI specifications in json format""" + app = create_basic_app() + typer.secho(json.dumps(app.openapi(), indent=2)) + + +# +# NOTE: We intentionally did NOT create a command to run the application +# Use instead $ uvicorn simcore_service_dynamic_sidecar.main:the_app +# diff --git a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/core/application.py b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/core/application.py index 0c59cb38101..5557d14450e 100644 --- a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/core/application.py +++ b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/core/application.py @@ -45,65 +45,72 @@ def setup_logger(settings: DynamicSidecarSettings): logging.root.setLevel(settings.log_level) -def assemble_application() -> FastAPI: - """ - Creates the application from using the env vars as a context - Also stores inside the state all instances of classes - needed in other requests and used to share data. - """ +def create_basic_app() -> FastAPI: + # settings settings = DynamicSidecarSettings.create_from_envs() setup_logger(settings) logger.debug(settings.json(indent=2)) - application = FastAPI( + # minimal + app = FastAPI( debug=settings.DEBUG, openapi_url=f"/api/{API_VTAG}/openapi.json", docs_url="/dev/doc", ) - override_fastapi_openapi_method(application) + override_fastapi_openapi_method(app) + app.state.settings = settings - application.state.settings = settings - application.state.shared_store = SharedStore() - application.state.application_health = ApplicationHealth() + app.include_router(main_router) + return app - # ROUTES -------------------- - application.include_router(main_router) - # ERROR HANDLERS ------------ - application.add_exception_handler(NodeNotFound, node_not_found_error_handler) - application.add_exception_handler(BaseDynamicSidecarError, http_error_handler) +def create_app(): + """ + Creates the application from using the env vars as a context + Also stores inside the state all instances of classes + needed in other requests and used to share data. + """ + + app = create_basic_app() # MODULES SETUP -------------- - if settings.is_development_mode: - remote_debug_setup(application) + app.state.shared_store = SharedStore() + app.state.application_health = ApplicationHealth() - if settings.RABBIT_SETTINGS: - setup_rabbitmq(application) - setup_background_log_fetcher(application) + if app.state.settings.is_development_mode: + remote_debug_setup(app) + + if app.state.settings.RABBIT_SETTINGS: + setup_rabbitmq(app) + setup_background_log_fetcher(app) # also sets up mounted_volumes - setup_mounted_fs(application) - setup_directory_watcher(application) + setup_mounted_fs(app) + setup_directory_watcher(app) + + # ERROR HANDLERS ------------ + app.add_exception_handler(NodeNotFound, node_not_found_error_handler) + app.add_exception_handler(BaseDynamicSidecarError, http_error_handler) # EVENTS --------------------- async def _on_startup() -> None: - await login_registry(settings.REGISTRY_SETTINGS) - await volumes_fix_permissions(application.state.mounted_volumes) + await login_registry(app.state.settings.REGISTRY_SETTINGS) + await volumes_fix_permissions(app.state.mounted_volumes) print(WELCOME_MSG, flush=True) async def _on_shutdown() -> None: logger.info("Going to remove spawned containers") result = await remove_the_compose_spec( - shared_store=application.state.shared_store, - settings=settings, - command_timeout=settings.DYNAMIC_SIDECAR_DOCKER_COMPOSE_DOWN_TIMEOUT, + shared_store=app.state.shared_store, + settings=app.state.settings, + command_timeout=app.state.settings.DYNAMIC_SIDECAR_DOCKER_COMPOSE_DOWN_TIMEOUT, ) logger.info("Container removal did_succeed=%s\n%s", result[0], result[1]) logger.info("shutdown cleanup completed") - application.add_event_handler("startup", _on_startup) - application.add_event_handler("shutdown", _on_shutdown) + app.add_event_handler("startup", _on_startup) + app.add_event_handler("shutdown", _on_shutdown) - return application + return app diff --git a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/main.py b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/main.py index afaad5a39d2..52c91f22837 100644 --- a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/main.py +++ b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/main.py @@ -2,7 +2,7 @@ """ from fastapi import FastAPI -from simcore_service_dynamic_sidecar.core.application import assemble_application +from simcore_service_dynamic_sidecar.core.application import create_app # SINGLETON FastAPI app -app: FastAPI = assemble_application() +the_app: FastAPI = create_app() diff --git a/services/dynamic-sidecar/tests/conftest.py b/services/dynamic-sidecar/tests/conftest.py index 35a820f7bee..56a236ffb5a 100644 --- a/services/dynamic-sidecar/tests/conftest.py +++ b/services/dynamic-sidecar/tests/conftest.py @@ -20,7 +20,7 @@ from fastapi import FastAPI from pytest_mock.plugin import MockerFixture from simcore_service_dynamic_sidecar.core import utils -from simcore_service_dynamic_sidecar.core.application import assemble_application +from simcore_service_dynamic_sidecar.core.application import create_app from simcore_service_dynamic_sidecar.core.docker_utils import docker_client from simcore_service_dynamic_sidecar.core.settings import DynamicSidecarSettings from simcore_service_dynamic_sidecar.core.shared_handlers import ( @@ -132,7 +132,7 @@ async def _mock_is_registry_reachable(*args, **kwargs) -> None: @pytest.fixture(scope="module") def app(mock_environment: None, disable_registry_check: None) -> FastAPI: - app = assemble_application() + app = create_app() app.state.rabbitmq = AsyncMock() return app diff --git a/services/dynamic-sidecar/tests/unit/test_core_docker_logs.py b/services/dynamic-sidecar/tests/unit/test_core_docker_logs.py index 2aab1cf48b3..8bd38e3e874 100644 --- a/services/dynamic-sidecar/tests/unit/test_core_docker_logs.py +++ b/services/dynamic-sidecar/tests/unit/test_core_docker_logs.py @@ -13,7 +13,7 @@ from _pytest.monkeypatch import MonkeyPatch from async_asgi_testclient import TestClient from fastapi import FastAPI -from simcore_service_dynamic_sidecar.core.application import assemble_application +from simcore_service_dynamic_sidecar.core.application import create_app from simcore_service_dynamic_sidecar.core.docker_logs import ( _get_background_log_fetcher, start_log_fetching, @@ -62,7 +62,7 @@ def app( monkeypatch_module.setenv("S3_SECURE", "false") monkeypatch_module.setenv("R_CLONE_PROVIDER", "MINIO") - yield assemble_application() + yield create_app() @pytest.fixture diff --git a/services/dynamic-sidecar/tests/unit/test_core_rabbitmq.py b/services/dynamic-sidecar/tests/unit/test_core_rabbitmq.py index d5872af86f0..eb7f50b917a 100644 --- a/services/dynamic-sidecar/tests/unit/test_core_rabbitmq.py +++ b/services/dynamic-sidecar/tests/unit/test_core_rabbitmq.py @@ -18,7 +18,7 @@ from models_library.users import UserID from pytest_mock.plugin import MockerFixture from settings_library.rabbit import RabbitSettings -from simcore_service_dynamic_sidecar.core.application import assemble_application +from simcore_service_dynamic_sidecar.core.application import create_app from simcore_service_dynamic_sidecar.core.rabbitmq import SLEEP_BETWEEN_SENDS, RabbitMQ from simcore_service_dynamic_sidecar.modules import mounted_fs @@ -114,7 +114,7 @@ def mock_environment( @pytest.fixture def app(mock_environment: None) -> FastAPI: - return assemble_application() + return create_app() # UTILS