diff --git a/Makefile b/Makefile index fcfc65a..08202cf 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ DOCKER_UID = $(shell id -u) DOCKER_GID = $(shell id -g) DOCKER_USER = $(DOCKER_UID):$(DOCKER_GID) -COMPOSE = DOCKER_USER=$(DOCKER_USER) docker-compose +COMPOSE = DOCKER_USER=$(DOCKER_USER) docker compose -f docker-compose.yml -f docker-compose-superset.yml COMPOSE_RUN = $(COMPOSE) run --rm # -- Node @@ -50,7 +50,8 @@ bootstrap: \ build \ compile \ fixtures \ - hooks + hooks \ + init-superset bootstrap: ## bootstrap the application .PHONY: bootstrap @@ -86,7 +87,7 @@ fixtures: ## Load test data (for development) @$(WAIT_MYSQL) zcat ./fixtures/elasticsearch/lrs.json.gz | \ $(COMPOSE_RUN) patch_statements_date | \ - $(COMPOSE_RUN) -T ralph push -b es --es-index statements-fixtures && \ + $(COMPOSE_RUN) -T ralph push -b es && \ $(COMPOSE_RUN) users-permissions sh /scripts/users-permissions.sh .PHONY: fixtures @@ -98,6 +99,10 @@ hooks: ## run post-deployment hooks @$(COMPOSE_RUN) hooks ./post-deploy .PHONY: hooks +init-superset: ## initialize superset + @$(COMPOSE) run superset-init +.PHONY: init-superset + lint: ## lint Jsonnet sources and libraries bin/jsonnet-lint $(sources) $(libraries) .PHONY: lint @@ -106,6 +111,10 @@ logs: ## display grafana logs (follow mode) @$(COMPOSE) logs -f grafana .PHONY: logs +logs-superset: ## display superset logs (follow mode) + @$(COMPOSE) logs -f superset +.PHONY: logs-superset + plugins: ## download, build and install plugins @$(YARN) build .PHONY: plugins @@ -131,6 +140,12 @@ run: ## start the development server @$(WAIT_GRAFANA) .PHONY: run +run-superset: \ + run +run-superset: ## start superset server + @$(COMPOSE) up -d superset superset-worker superset-worker-beat +.PHONY: run-superset + status: ## an alias for "docker-compose ps" @$(COMPOSE) ps .PHONY: status diff --git a/bin/compose b/bin/compose index 15538fa..9f976ba 100755 --- a/bin/compose +++ b/bin/compose @@ -5,5 +5,7 @@ declare DOCKER_GID="$(id -g)" DOCKER_UID=${DOCKER_UID} \ DOCKER_GID=${DOCKER_GID} \ - docker-compose \ + docker compose \ + -f docker-compose.yml \ + -f docker-compose-superset.yml \ "$@" diff --git a/docker-compose-superset.yml b/docker-compose-superset.yml new file mode 100644 index 0000000..d6e6199 --- /dev/null +++ b/docker-compose-superset.yml @@ -0,0 +1,90 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +x-superset-image: &superset-image apache/superset:${TAG:-latest-dev} +x-superset-depends-on: &superset-depends-on + - db + - redis +x-superset-volumes: &superset-volumes + # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container + - ./superset:/app/docker + - superset_home:/app/superset_home + +version: "3.7" +services: + redis: + image: redis:latest + container_name: superset_cache + restart: unless-stopped + volumes: + - redis:/data + + db: + env_file: superset/.env-non-dev + image: postgres:10 + container_name: superset_db + restart: unless-stopped + volumes: + - db_home:/var/lib/postgresql/data + + superset: + env_file: superset/.env-non-dev + image: *superset-image + container_name: superset_app + command: ["/app/docker/docker-bootstrap.sh", "app-gunicorn"] + user: "root" + restart: unless-stopped + ports: + - 8088:8088 + depends_on: *superset-depends-on + volumes: *superset-volumes + + superset-init: + image: *superset-image + container_name: superset_init + command: ["/app/docker/docker-init.sh"] + env_file: superset/.env-non-dev + depends_on: *superset-depends-on + user: "root" + volumes: *superset-volumes + + superset-worker: + image: *superset-image + container_name: superset_worker + command: ["/app/docker/docker-bootstrap.sh", "worker"] + env_file: superset/.env-non-dev + restart: unless-stopped + depends_on: *superset-depends-on + user: "root" + volumes: *superset-volumes + + superset-worker-beat: + image: *superset-image + container_name: superset_worker_beat + command: ["/app/docker/docker-bootstrap.sh", "beat"] + env_file: superset/.env-non-dev + restart: unless-stopped + depends_on: *superset-depends-on + user: "root" + volumes: *superset-volumes + +volumes: + superset_home: + external: false + db_home: + external: false + redis: + external: false diff --git a/docker-compose.yml b/docker-compose.yml index c2b11dd..53637ba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,8 +77,8 @@ services: user: "${DOCKER_USER:-1000}" entrypoint: ["ralph"] environment: - - RALPH_ES_HOSTS=http://elasticsearch:9200 - - RALPH_ES_INDEX=statements + - RALPH_BACKENDS__DATABASE__ES__HOSTS=http://elasticsearch:9200 + - RALPH_BACKENDS__DATABASE__ES__INDEX=statements elasticsearch: image: elasticsearch:8.2.0 diff --git a/superset/.env b/superset/.env new file mode 100644 index 0000000..b2f11c1 --- /dev/null +++ b/superset/.env @@ -0,0 +1,46 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +COMPOSE_PROJECT_NAME=superset + +# database configurations (do not modify) +DATABASE_DB=superset +DATABASE_HOST=db +DATABASE_PASSWORD=superset +DATABASE_USER=superset + +# database engine specific environment variables +# change the below if you prefers another database engine +DATABASE_PORT=5432 +DATABASE_DIALECT=postgresql +POSTGRES_DB=superset +POSTGRES_USER=superset +POSTGRES_PASSWORD=superset +#MYSQL_DATABASE=superset +#MYSQL_USER=superset +#MYSQL_PASSWORD=superset +#MYSQL_RANDOM_ROOT_PASSWORD=yes + +# Add the mapped in /app/pythonpath_docker which allows devs to override stuff +PYTHONPATH=/app/pythonpath:/app/docker/pythonpath_dev +REDIS_HOST=redis +REDIS_PORT=6379 + +FLASK_ENV=development +SUPERSET_ENV=development +SUPERSET_LOAD_EXAMPLES=yes +CYPRESS_CONFIG=false +SUPERSET_PORT=8088 diff --git a/superset/.env-non-dev b/superset/.env-non-dev new file mode 100644 index 0000000..1cb5d30 --- /dev/null +++ b/superset/.env-non-dev @@ -0,0 +1,46 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +COMPOSE_PROJECT_NAME=superset + +# database configurations (do not modify) +DATABASE_DB=superset +DATABASE_HOST=db +DATABASE_PASSWORD=superset +DATABASE_USER=superset + +# database engine specific environment variables +# change the below if you prefers another database engine +DATABASE_PORT=5432 +DATABASE_DIALECT=postgresql +POSTGRES_DB=superset +POSTGRES_USER=superset +POSTGRES_PASSWORD=superset +#MYSQL_DATABASE=superset +#MYSQL_USER=superset +#MYSQL_PASSWORD=superset +#MYSQL_RANDOM_ROOT_PASSWORD=yes + +# Add the mapped in /app/pythonpath_docker which allows devs to override stuff +PYTHONPATH=/app/pythonpath:/app/docker/pythonpath_dev +REDIS_HOST=redis +REDIS_PORT=6379 + +FLASK_ENV=production +SUPERSET_ENV=production +SUPERSET_LOAD_EXAMPLES=yes +CYPRESS_CONFIG=false +SUPERSET_PORT=8088 diff --git a/superset/README.md b/superset/README.md new file mode 100644 index 0000000..c867121 --- /dev/null +++ b/superset/README.md @@ -0,0 +1,75 @@ + + +# Getting Started with Superset using Docker + +Docker is an easy way to get started with Superset. + +## Prerequisites + +1. Docker! [link](https://www.docker.com/get-started) +2. Docker-compose [link](https://docs.docker.com/compose/install/) + +## Configuration + +The `/app/pythonpath` folder is mounted from [`./docker/pythonpath_dev`](./pythonpath_dev) +which contains a base configuration [`./docker/pythonpath_dev/superset_config.py`](./pythonpath_dev/superset_config.py) +intended for use with local development. + +### Local overrides + +In order to override configuration settings locally, simply make a copy of [`./docker/pythonpath_dev/superset_config_local.example`](./pythonpath_dev/superset_config_local.example) +into `./docker/pythonpath_dev/superset_config_docker.py` (git ignored) and fill in your overrides. + +### Local packages + +If you want to add Python packages in order to test things like databases locally, you can simply add a local requirements.txt (`./docker/requirements-local.txt`) +and rebuild your Docker stack. + +Steps: + +1. Create `./docker/requirements-local.txt` +2. Add your new packages +3. Rebuild docker-compose + 1. `docker-compose down -v` + 2. `docker-compose up` + +## Initializing Database + +The database will initialize itself upon startup via the init container ([`superset-init`](./docker-init.sh)). This may take a minute. + +## Normal Operation + +To run the container, simply run: `docker-compose up` + +After waiting several minutes for Superset initialization to finish, you can open a browser and view [`http://localhost:8088`](http://localhost:8088) +to start your journey. + +## Developing + +While running, the container server will reload on modification of the Superset Python and JavaScript source code. +Don't forget to reload the page to take the new frontend into account though. + +## Production + +It is possible to run Superset in non-development mode by using [`docker-compose-non-dev.yml`](../docker-compose-non-dev.yml). This file excludes the volumes needed for development and uses [`./docker/.env-non-dev`](./.env-non-dev) which sets the variable `SUPERSET_ENV` to `production`. + +## Resource Constraints + +If you are attempting to build on macOS and it exits with 137 you need to increase your Docker resources. See instructions [here](https://docs.docker.com/docker-for-mac/#advanced) (search for memory) diff --git a/superset/docker-bootstrap.sh b/superset/docker-bootstrap.sh new file mode 100755 index 0000000..67e5294 --- /dev/null +++ b/superset/docker-bootstrap.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -eo pipefail + +REQUIREMENTS_LOCAL="/app/docker/requirements-local.txt" +# If Cypress run – overwrite the password for admin and export env variables +if [ "$CYPRESS_CONFIG" == "true" ]; then + export SUPERSET_CONFIG=tests.integration_tests.superset_test_config + export SUPERSET_TESTENV=true + export ENABLE_REACT_CRUD_VIEWS=true + export SUPERSET__SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://superset:superset@db:5432/superset +fi +# +# Make sure we have dev requirements installed +# +if [ -f "${REQUIREMENTS_LOCAL}" ]; then + echo "Installing local overrides at ${REQUIREMENTS_LOCAL}" + pip install -r "${REQUIREMENTS_LOCAL}" +else + echo "Skipping local overrides" +fi + +if [[ "${1}" == "worker" ]]; then + echo "Starting Celery worker..." + celery --app=superset.tasks.celery_app:app worker -Ofair -l INFO +elif [[ "${1}" == "beat" ]]; then + echo "Starting Celery beat..." + celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid -l INFO -s "${SUPERSET_HOME}"/celerybeat-schedule +elif [[ "${1}" == "app" ]]; then + echo "Starting web app..." + flask run -p 8088 --with-threads --reload --debugger --host=0.0.0.0 +elif [[ "${1}" == "app-gunicorn" ]]; then + echo "Starting web app..." + /usr/bin/run-server.sh +fi diff --git a/superset/docker-ci.sh b/superset/docker-ci.sh new file mode 100755 index 0000000..9e97cbb --- /dev/null +++ b/superset/docker-ci.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +/app/docker/docker-init.sh + +# TODO: copy config overrides from ENV vars + +# TODO: run celery in detached state +export SERVER_THREADS_AMOUNT=8 +# start up the web server + +/usr/bin/run-server.sh diff --git a/superset/docker-frontend.sh b/superset/docker-frontend.sh new file mode 100755 index 0000000..4c0d01e --- /dev/null +++ b/superset/docker-frontend.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +set -e + +cd /app/superset-frontend +npm install -g npm@7 +npm install -f --no-optional --global webpack webpack-cli +npm install -f --no-optional + +echo "Running frontend" +npm run dev diff --git a/superset/docker-init.sh b/superset/docker-init.sh new file mode 100755 index 0000000..c928c1b --- /dev/null +++ b/superset/docker-init.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +set -e + +# +# Always install local overrides first +# +/app/docker/docker-bootstrap.sh + +STEP_CNT=4 + +echo_step() { +cat < str: + """Get the environment variable or raise exception.""" + try: + return os.environ[var_name] + except KeyError: + if default is not None: + return default + else: + error_msg = "The environment variable {} was missing, abort...".format( + var_name + ) + raise EnvironmentError(error_msg) + + +DATABASE_DIALECT = get_env_variable("DATABASE_DIALECT") +DATABASE_USER = get_env_variable("DATABASE_USER") +DATABASE_PASSWORD = get_env_variable("DATABASE_PASSWORD") +DATABASE_HOST = get_env_variable("DATABASE_HOST") +DATABASE_PORT = get_env_variable("DATABASE_PORT") +DATABASE_DB = get_env_variable("DATABASE_DB") + +# The SQLAlchemy connection string. +SQLALCHEMY_DATABASE_URI = "%s://%s:%s@%s:%s/%s" % ( + DATABASE_DIALECT, + DATABASE_USER, + DATABASE_PASSWORD, + DATABASE_HOST, + DATABASE_PORT, + DATABASE_DB, +) + +REDIS_HOST = get_env_variable("REDIS_HOST") +REDIS_PORT = get_env_variable("REDIS_PORT") +REDIS_CELERY_DB = get_env_variable("REDIS_CELERY_DB", "0") +REDIS_RESULTS_DB = get_env_variable("REDIS_RESULTS_DB", "1") + +RESULTS_BACKEND = FileSystemCache("/app/superset_home/sqllab") + +CACHE_CONFIG = { + "CACHE_TYPE": "redis", + "CACHE_DEFAULT_TIMEOUT": 300, + "CACHE_KEY_PREFIX": "superset_", + "CACHE_REDIS_HOST": REDIS_HOST, + "CACHE_REDIS_PORT": REDIS_PORT, + "CACHE_REDIS_DB": REDIS_RESULTS_DB, +} +DATA_CACHE_CONFIG = CACHE_CONFIG + + +class CeleryConfig(object): + BROKER_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CELERY_DB}" + CELERY_IMPORTS = ("superset.sql_lab", "superset.tasks") + CELERY_RESULT_BACKEND = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_RESULTS_DB}" + CELERYD_LOG_LEVEL = "DEBUG" + CELERYD_PREFETCH_MULTIPLIER = 1 + CELERY_ACKS_LATE = False + CELERYBEAT_SCHEDULE = { + "reports.scheduler": { + "task": "reports.scheduler", + "schedule": crontab(minute="*", hour="*"), + }, + "reports.prune_log": { + "task": "reports.prune_log", + "schedule": crontab(minute=10, hour=0), + }, + } + + +CELERY_CONFIG = CeleryConfig + +FEATURE_FLAGS = {"ALERT_REPORTS": True} +ALERT_REPORTS_NOTIFICATION_DRY_RUN = True +WEBDRIVER_BASEURL = "http://superset:8088/" +# The base URL for the email report hyperlinks. +WEBDRIVER_BASEURL_USER_FRIENDLY = WEBDRIVER_BASEURL + +SQLLAB_CTAS_NO_LIMIT = True + +# i18n +LANGUAGES = { + "en": {"flag": "us", "name": "English"}, + "fr": {"flag": "fr", "name": "French"}, +} + +# +# Optionally import superset_config_docker.py (which will have been included on +# the PYTHONPATH) in order to allow for local settings to be overridden +# +try: + import superset_config_docker + from superset_config_docker import * # noqa + + logger.info( + f"Loaded your Docker configuration at " f"[{superset_config_docker.__file__}]" + ) +except ImportError: + logger.info("Using default Docker config...") diff --git a/superset/pythonpath_dev/superset_config_local.example b/superset/pythonpath_dev/superset_config_local.example new file mode 100644 index 0000000..dfa03bb --- /dev/null +++ b/superset/pythonpath_dev/superset_config_local.example @@ -0,0 +1,27 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# This is an example "local" configuration file. In order to set/override config +# options that ONLY apply to your local environment, simply copy/rename this file +# to docker/pythonpath/superset_config_docker.py +# It ends up being imported by docker/superset_config.py which is loaded by +# superset/config.py +# + +SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://pguser:pgpwd@some.host/superset" +SQLALCHEMY_ECHO = True diff --git a/superset/requirements-local.txt b/superset/requirements-local.txt new file mode 100644 index 0000000..7673d50 --- /dev/null +++ b/superset/requirements-local.txt @@ -0,0 +1 @@ +elasticsearch-dbapi diff --git a/superset/run-server.sh b/superset/run-server.sh new file mode 100644 index 0000000..064f47b --- /dev/null +++ b/superset/run-server.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +HYPHEN_SYMBOL='-' + +gunicorn \ + --bind "${SUPERSET_BIND_ADDRESS:-0.0.0.0}:${SUPERSET_PORT:-8088}" \ + --access-logfile "${ACCESS_LOG_FILE:-$HYPHEN_SYMBOL}" \ + --error-logfile "${ERROR_LOG_FILE:-$HYPHEN_SYMBOL}" \ + --workers ${SERVER_WORKER_AMOUNT:-1} \ + --worker-class ${SERVER_WORKER_CLASS:-gthread} \ + --threads ${SERVER_THREADS_AMOUNT:-20} \ + --timeout ${GUNICORN_TIMEOUT:-60} \ + --keep-alive ${GUNICORN_KEEPALIVE:-2} \ + --limit-request-line ${SERVER_LIMIT_REQUEST_LINE:-0} \ + --limit-request-field_size ${SERVER_LIMIT_REQUEST_FIELD_SIZE:-0} \ + "${FLASK_APP}"