From f509de0a551fd77afe3bc906fa4380e4fa8bfe9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 8 Feb 2019 14:25:04 +0400 Subject: [PATCH] :sparkles: Add support for /app/prestart.sh --- README.md | 40 +++++++++- python3.6-alpine3.8/Dockerfile | 7 ++ python3.6-alpine3.8/app/prestart.sh | 12 +++ python3.6-alpine3.8/entrypoint.sh | 6 +- python3.6-alpine3.8/start.sh | 15 ++++ python3.6/Dockerfile | 7 ++ python3.6/app/prestart.sh | 12 +++ python3.6/entrypoint.sh | 6 +- python3.6/start.sh | 15 ++++ python3.7-alpine3.8/Dockerfile | 7 ++ python3.7-alpine3.8/app/prestart.sh | 12 +++ python3.7-alpine3.8/entrypoint.sh | 6 +- python3.7-alpine3.8/start.sh | 15 ++++ python3.7/Dockerfile | 7 ++ python3.7/app/prestart.sh | 12 +++ python3.7/entrypoint.sh | 6 +- python3.7/start.sh | 15 ++++ tests/test_01_main/test_defaults.py | 37 ++++++--- tests/test_01_main/test_env_vars_1.py | 35 ++++++--- tests/test_01_main/test_env_vars_2.py | 36 ++++++--- tests/test_01_main/test_env_vars_3.py | 31 ++++++-- tests/test_02_app/custom_app/app/prestart.sh | 3 + .../simple_app_prestart/app/main.py | 8 ++ .../simple_app_prestart/app/prestart.sh | 3 + .../simple_app_prestart/latest.dockerfile | 2 + .../python3.6-alpine3.8.dockerfile | 3 + .../simple_app_prestart/python3.6.dockerfile | 3 + .../python3.7-alpine3.8.dockerfile | 3 + .../simple_app_prestart/python3.7.dockerfile | 3 + tests/test_02_app/test_custom_app.py | 41 +++++++--- tests/test_02_app/test_package_app.py | 48 +++++++++--- tests/test_02_app/test_package_app_config.py | 36 ++++++--- .../test_package_app_custom_config.py | 47 ++++++++--- .../test_package_app_sub_config.py | 41 +++++++--- tests/test_02_app/test_simple_app.py | 48 +++++++++--- tests/test_02_app/test_simple_app_prestart.py | 77 +++++++++++++++++++ tests/utils.py | 7 +- 37 files changed, 582 insertions(+), 130 deletions(-) create mode 100644 python3.6-alpine3.8/app/prestart.sh create mode 100644 python3.6-alpine3.8/start.sh create mode 100644 python3.6/app/prestart.sh create mode 100644 python3.6/start.sh create mode 100644 python3.7-alpine3.8/app/prestart.sh create mode 100644 python3.7-alpine3.8/start.sh create mode 100644 python3.7/app/prestart.sh create mode 100644 python3.7/start.sh create mode 100644 tests/test_02_app/custom_app/app/prestart.sh create mode 100644 tests/test_02_app/simple_app_prestart/app/main.py create mode 100644 tests/test_02_app/simple_app_prestart/app/prestart.sh create mode 100644 tests/test_02_app/simple_app_prestart/latest.dockerfile create mode 100644 tests/test_02_app/simple_app_prestart/python3.6-alpine3.8.dockerfile create mode 100644 tests/test_02_app/simple_app_prestart/python3.6.dockerfile create mode 100644 tests/test_02_app/simple_app_prestart/python3.7-alpine3.8.dockerfile create mode 100644 tests/test_02_app/simple_app_prestart/python3.7.dockerfile create mode 100644 tests/test_02_app/test_simple_app_prestart.py diff --git a/README.md b/README.md index 260fb35..d2f4be0 100644 --- a/README.md +++ b/README.md @@ -294,9 +294,36 @@ It uses the environment variables declared above to set all the configurations. You can override it by including a file in: -* `/app/gunicorn_conf.py`, -* `/app/app/gunicorn_conf.py` or replacing the one in -* `/gunicorn_conf.py` or replacing the one in +* `/app/gunicorn_conf.py` +* `/app/app/gunicorn_conf.py` +* `/gunicorn_conf.py` + + +### Custom `/app/prestart.sh` + +If you need to run anything before starting the app, you can add a file `prestart.sh` to the directory `/app`. The image will automatically detect and run it before starting everything. + +For example, if you want to add Alembic SQL migrations (with SQLALchemy), you could create a `./app/prestart.sh` file in your code directory (that will be copied by your `Dockerfile`) with: + +```bash +#! /usr/bin/env bash + +# Let the DB start +sleep 10; +# Run migrations +alembic upgrade head +``` + +and it would wait 10 seconds to give the database some time to start and then run that `alembic` command. + +If you need to run a Python script before starting the app, you could make the `/app/prestart.sh` file run your Python script, with something like: + +```bash +#! /usr/bin/env bash + +# Run custom Python script before starting +python /app/my_custom_prestart_script.py +``` ## Tests @@ -304,6 +331,13 @@ You can override it by including a file in: All the image tags, configurations, environment variables and application options are tested. +## Release Notes + +### 0.1.0 + +* Add support for `/app/prestart.sh`. + + ## License This project is licensed under the terms of the MIT license. diff --git a/python3.6-alpine3.8/Dockerfile b/python3.6-alpine3.8/Dockerfile index c7947a5..2596c56 100644 --- a/python3.6-alpine3.8/Dockerfile +++ b/python3.6-alpine3.8/Dockerfile @@ -9,6 +9,9 @@ RUN apk add --no-cache --virtual .build-deps gcc libc-dev \ COPY ./entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh +COPY ./start.sh /start.sh +RUN chmod +x /start.sh + COPY ./gunicorn_conf.py /gunicorn_conf.py COPY ./app /app @@ -19,3 +22,7 @@ ENV PYTHONPATH=/app EXPOSE 80 ENTRYPOINT ["/entrypoint.sh"] + +# Run the start script, it will check for an /app/prestart.sh script (e.g. for migrations) +# And then will start Gunicorn with Meinheld +CMD ["/start.sh"] diff --git a/python3.6-alpine3.8/app/prestart.sh b/python3.6-alpine3.8/app/prestart.sh new file mode 100644 index 0000000..9ccb1da --- /dev/null +++ b/python3.6-alpine3.8/app/prestart.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env sh + +echo "Running inside /app/prestart.sh, you could add migrations to this file, e.g.:" + +echo " +#! /usr/bin/env bash + +# Let the DB start +sleep 10; +# Run migrations +alembic upgrade head +" diff --git a/python3.6-alpine3.8/entrypoint.sh b/python3.6-alpine3.8/entrypoint.sh index 497fb9d..bcf66db 100644 --- a/python3.6-alpine3.8/entrypoint.sh +++ b/python3.6-alpine3.8/entrypoint.sh @@ -19,8 +19,4 @@ else fi export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF} -if [ -z "$@" ]; then - gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE" -else - exec "$@" -fi +exec "$@" diff --git a/python3.6-alpine3.8/start.sh b/python3.6-alpine3.8/start.sh new file mode 100644 index 0000000..45951c5 --- /dev/null +++ b/python3.6-alpine3.8/start.sh @@ -0,0 +1,15 @@ +#! /usr/bin/env sh +set -e + +# If there's a prestart.sh script in the /app directory, run it before starting +PRE_START_PATH=/app/prestart.sh +echo "Checking for script in $PRE_START_PATH" +if [ -f $PRE_START_PATH ] ; then + echo "Running script $PRE_START_PATH" + . "$PRE_START_PATH" +else + echo "There is no script $PRE_START_PATH" +fi + +# Start Gunicorn +exec gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE" diff --git a/python3.6/Dockerfile b/python3.6/Dockerfile index 0af35aa..0392916 100644 --- a/python3.6/Dockerfile +++ b/python3.6/Dockerfile @@ -7,6 +7,9 @@ RUN pip install meinheld gunicorn COPY ./entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh +COPY ./start.sh /start.sh +RUN chmod +x /start.sh + COPY ./gunicorn_conf.py /gunicorn_conf.py COPY ./app /app @@ -17,3 +20,7 @@ ENV PYTHONPATH=/app EXPOSE 80 ENTRYPOINT ["/entrypoint.sh"] + +# Run the start script, it will check for an /app/prestart.sh script (e.g. for migrations) +# And then will start Gunicorn with Meinheld +CMD ["/start.sh"] diff --git a/python3.6/app/prestart.sh b/python3.6/app/prestart.sh new file mode 100644 index 0000000..9ccb1da --- /dev/null +++ b/python3.6/app/prestart.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env sh + +echo "Running inside /app/prestart.sh, you could add migrations to this file, e.g.:" + +echo " +#! /usr/bin/env bash + +# Let the DB start +sleep 10; +# Run migrations +alembic upgrade head +" diff --git a/python3.6/entrypoint.sh b/python3.6/entrypoint.sh index 497fb9d..bcf66db 100644 --- a/python3.6/entrypoint.sh +++ b/python3.6/entrypoint.sh @@ -19,8 +19,4 @@ else fi export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF} -if [ -z "$@" ]; then - gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE" -else - exec "$@" -fi +exec "$@" diff --git a/python3.6/start.sh b/python3.6/start.sh new file mode 100644 index 0000000..45951c5 --- /dev/null +++ b/python3.6/start.sh @@ -0,0 +1,15 @@ +#! /usr/bin/env sh +set -e + +# If there's a prestart.sh script in the /app directory, run it before starting +PRE_START_PATH=/app/prestart.sh +echo "Checking for script in $PRE_START_PATH" +if [ -f $PRE_START_PATH ] ; then + echo "Running script $PRE_START_PATH" + . "$PRE_START_PATH" +else + echo "There is no script $PRE_START_PATH" +fi + +# Start Gunicorn +exec gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE" diff --git a/python3.7-alpine3.8/Dockerfile b/python3.7-alpine3.8/Dockerfile index 4cf5465..a4f724e 100644 --- a/python3.7-alpine3.8/Dockerfile +++ b/python3.7-alpine3.8/Dockerfile @@ -9,6 +9,9 @@ RUN apk add --no-cache --virtual .build-deps gcc libc-dev \ COPY ./entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh +COPY ./start.sh /start.sh +RUN chmod +x /start.sh + COPY ./gunicorn_conf.py /gunicorn_conf.py COPY ./app /app @@ -19,3 +22,7 @@ ENV PYTHONPATH=/app EXPOSE 80 ENTRYPOINT ["/entrypoint.sh"] + +# Run the start script, it will check for an /app/prestart.sh script (e.g. for migrations) +# And then will start Gunicorn with Meinheld +CMD ["/start.sh"] diff --git a/python3.7-alpine3.8/app/prestart.sh b/python3.7-alpine3.8/app/prestart.sh new file mode 100644 index 0000000..9ccb1da --- /dev/null +++ b/python3.7-alpine3.8/app/prestart.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env sh + +echo "Running inside /app/prestart.sh, you could add migrations to this file, e.g.:" + +echo " +#! /usr/bin/env bash + +# Let the DB start +sleep 10; +# Run migrations +alembic upgrade head +" diff --git a/python3.7-alpine3.8/entrypoint.sh b/python3.7-alpine3.8/entrypoint.sh index 497fb9d..bcf66db 100644 --- a/python3.7-alpine3.8/entrypoint.sh +++ b/python3.7-alpine3.8/entrypoint.sh @@ -19,8 +19,4 @@ else fi export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF} -if [ -z "$@" ]; then - gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE" -else - exec "$@" -fi +exec "$@" diff --git a/python3.7-alpine3.8/start.sh b/python3.7-alpine3.8/start.sh new file mode 100644 index 0000000..45951c5 --- /dev/null +++ b/python3.7-alpine3.8/start.sh @@ -0,0 +1,15 @@ +#! /usr/bin/env sh +set -e + +# If there's a prestart.sh script in the /app directory, run it before starting +PRE_START_PATH=/app/prestart.sh +echo "Checking for script in $PRE_START_PATH" +if [ -f $PRE_START_PATH ] ; then + echo "Running script $PRE_START_PATH" + . "$PRE_START_PATH" +else + echo "There is no script $PRE_START_PATH" +fi + +# Start Gunicorn +exec gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE" diff --git a/python3.7/Dockerfile b/python3.7/Dockerfile index 5eb0ed8..4e76a32 100644 --- a/python3.7/Dockerfile +++ b/python3.7/Dockerfile @@ -7,6 +7,9 @@ RUN pip install meinheld gunicorn COPY ./entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh +COPY ./start.sh /start.sh +RUN chmod +x /start.sh + COPY ./gunicorn_conf.py /gunicorn_conf.py COPY ./app /app @@ -17,3 +20,7 @@ ENV PYTHONPATH=/app EXPOSE 80 ENTRYPOINT ["/entrypoint.sh"] + +# Run the start script, it will check for an /app/prestart.sh script (e.g. for migrations) +# And then will start Gunicorn with Meinheld +CMD ["/start.sh"] diff --git a/python3.7/app/prestart.sh b/python3.7/app/prestart.sh new file mode 100644 index 0000000..9ccb1da --- /dev/null +++ b/python3.7/app/prestart.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env sh + +echo "Running inside /app/prestart.sh, you could add migrations to this file, e.g.:" + +echo " +#! /usr/bin/env bash + +# Let the DB start +sleep 10; +# Run migrations +alembic upgrade head +" diff --git a/python3.7/entrypoint.sh b/python3.7/entrypoint.sh index 497fb9d..bcf66db 100644 --- a/python3.7/entrypoint.sh +++ b/python3.7/entrypoint.sh @@ -19,8 +19,4 @@ else fi export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF} -if [ -z "$@" ]; then - gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE" -else - exec "$@" -fi +exec "$@" diff --git a/python3.7/start.sh b/python3.7/start.sh new file mode 100644 index 0000000..45951c5 --- /dev/null +++ b/python3.7/start.sh @@ -0,0 +1,15 @@ +#! /usr/bin/env sh +set -e + +# If there's a prestart.sh script in the /app directory, run it before starting +PRE_START_PATH=/app/prestart.sh +echo "Checking for script in $PRE_START_PATH" +if [ -f $PRE_START_PATH ] ; then + echo "Running script $PRE_START_PATH" + . "$PRE_START_PATH" +else + echo "There is no script $PRE_START_PATH" +fi + +# Start Gunicorn +exec gunicorn -k egg:meinheld#gunicorn_worker -c "$GUNICORN_CONF" "$APP_MODULE" diff --git a/tests/test_01_main/test_defaults.py b/tests/test_01_main/test_defaults.py index 10e6f5e..17b813e 100644 --- a/tests/test_01_main/test_defaults.py +++ b/tests/test_01_main/test_defaults.py @@ -4,11 +4,29 @@ import pytest import requests -from ..utils import CONTAINER_NAME, get_config, stop_previous_container +from ..utils import CONTAINER_NAME, get_config, get_logs, remove_previous_container client = docker.from_env() +def verify_container(container, response_text): + config_data = get_config(container) + assert config_data["workers_per_core"] == 2 + assert config_data["host"] == "0.0.0.0" + assert config_data["port"] == "80" + assert config_data["loglevel"] == "info" + assert config_data["workers"] > 2 + assert config_data["bind"] == "0.0.0.0:80" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert ( + "Running inside /app/prestart.sh, you could add migrations to this file" in logs + ) + response = requests.get("http://127.0.0.1:8000") + assert response.text == response_text + + @pytest.mark.parametrize( "image,response_text", [ @@ -35,19 +53,16 @@ ], ) def test_defaults(image, response_text): - stop_previous_container(client) + remove_previous_container(client) container = client.containers.run( image, name=CONTAINER_NAME, ports={"80": "8000"}, detach=True ) - config_data = get_config(container) - assert config_data["workers_per_core"] == 2 - assert config_data["host"] == "0.0.0.0" - assert config_data["port"] == "80" - assert config_data["loglevel"] == "info" - assert config_data["workers"] > 2 - assert config_data["bind"] == "0.0.0.0:80" time.sleep(1) - response = requests.get("http://127.0.0.1:8000") - assert response.text == response_text + verify_container(container, response_text) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container, response_text) container.stop() container.remove() diff --git a/tests/test_01_main/test_env_vars_1.py b/tests/test_01_main/test_env_vars_1.py index 6ae82d7..fab1ebf 100644 --- a/tests/test_01_main/test_env_vars_1.py +++ b/tests/test_01_main/test_env_vars_1.py @@ -4,11 +4,28 @@ import pytest import requests -from ..utils import CONTAINER_NAME, get_config, stop_previous_container +from ..utils import CONTAINER_NAME, get_config, get_logs, remove_previous_container client = docker.from_env() +def verify_container(container, response_text): + config_data = get_config(container) + assert config_data["workers_per_core"] == 1 + assert config_data["host"] == "0.0.0.0" + assert config_data["port"] == "8000" + assert config_data["loglevel"] == "warning" + assert config_data["bind"] == "0.0.0.0:8000" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert ( + "Running inside /app/prestart.sh, you could add migrations to this file" in logs + ) + response = requests.get("http://127.0.0.1:8000") + assert response.text == response_text + + @pytest.mark.parametrize( "image,response_text", [ @@ -35,7 +52,7 @@ ], ) def test_env_vars_1(image, response_text): - stop_previous_container(client) + remove_previous_container(client) container = client.containers.run( image, name=CONTAINER_NAME, @@ -43,14 +60,12 @@ def test_env_vars_1(image, response_text): ports={"8000": "8000"}, detach=True, ) - config_data = get_config(container) - assert config_data["workers_per_core"] == 1 - assert config_data["host"] == "0.0.0.0" - assert config_data["port"] == "8000" - assert config_data["loglevel"] == "warning" - assert config_data["bind"] == "0.0.0.0:8000" time.sleep(1) - response = requests.get("http://127.0.0.1:8000") - assert response.text == response_text + verify_container(container, response_text) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container, response_text) container.stop() container.remove() diff --git a/tests/test_01_main/test_env_vars_2.py b/tests/test_01_main/test_env_vars_2.py index ca3fab4..90cdde5 100644 --- a/tests/test_01_main/test_env_vars_2.py +++ b/tests/test_01_main/test_env_vars_2.py @@ -6,13 +6,31 @@ from ..utils import ( CONTAINER_NAME, get_config, + get_logs, get_process_names, - stop_previous_container, + remove_previous_container, ) client = docker.from_env() +def verify_container(container): + process_names = get_process_names(container) + config_data = get_config(container) + assert config_data["workers"] == 1 + assert len(process_names) == 2 # Manager + worker + assert config_data["host"] == "127.0.0.1" + assert config_data["port"] == "80" + assert config_data["loglevel"] == "info" + assert config_data["bind"] == "127.0.0.1:80" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert ( + "Running inside /app/prestart.sh, you could add migrations to this file" in logs + ) + + @pytest.mark.parametrize( "image", [ @@ -24,7 +42,7 @@ ], ) def test_env_vars_2(image): - stop_previous_container(client) + remove_previous_container(client) container = client.containers.run( image, name=CONTAINER_NAME, @@ -33,13 +51,11 @@ def test_env_vars_2(image): detach=True, ) time.sleep(1) - process_names = get_process_names(container) - config_data = get_config(container) - assert config_data["workers"] == 1 - assert len(process_names) == 2 # Manager + worker - assert config_data["host"] == "127.0.0.1" - assert config_data["port"] == "80" - assert config_data["loglevel"] == "info" - assert config_data["bind"] == "127.0.0.1:80" + verify_container(container) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container) container.stop() container.remove() diff --git a/tests/test_01_main/test_env_vars_3.py b/tests/test_01_main/test_env_vars_3.py index a654740..914fae7 100644 --- a/tests/test_01_main/test_env_vars_3.py +++ b/tests/test_01_main/test_env_vars_3.py @@ -4,11 +4,26 @@ import pytest import requests -from ..utils import CONTAINER_NAME, get_config, stop_previous_container +from ..utils import CONTAINER_NAME, get_config, get_logs, remove_previous_container client = docker.from_env() +def verify_container(container, response_text): + config_data = get_config(container) + assert config_data["host"] == "127.0.0.1" + assert config_data["port"] == "9000" + assert config_data["bind"] == "0.0.0.0:8080" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert ( + "Running inside /app/prestart.sh, you could add migrations to this file" in logs + ) + response = requests.get("http://127.0.0.1:8000") + assert response.text == response_text + + @pytest.mark.parametrize( "image,response_text", [ @@ -35,7 +50,7 @@ ], ) def test_env_bind(image, response_text): - stop_previous_container(client) + remove_previous_container(client) container = client.containers.run( image, name=CONTAINER_NAME, @@ -43,12 +58,12 @@ def test_env_bind(image, response_text): ports={"8080": "8000"}, detach=True, ) - config_data = get_config(container) - assert config_data["host"] == "127.0.0.1" - assert config_data["port"] == "9000" - assert config_data["bind"] == "0.0.0.0:8080" time.sleep(1) - response = requests.get("http://127.0.0.1:8000") - assert response.text == response_text + verify_container(container, response_text) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container, response_text) container.stop() container.remove() diff --git a/tests/test_02_app/custom_app/app/prestart.sh b/tests/test_02_app/custom_app/app/prestart.sh new file mode 100644 index 0000000..4fc70af --- /dev/null +++ b/tests/test_02_app/custom_app/app/prestart.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env sh + +echo "Running custom prestart.sh" diff --git a/tests/test_02_app/simple_app_prestart/app/main.py b/tests/test_02_app/simple_app_prestart/app/main.py new file mode 100644 index 0000000..8d9ad92 --- /dev/null +++ b/tests/test_02_app/simple_app_prestart/app/main.py @@ -0,0 +1,8 @@ +import sys + + +def app(env, start_response): + version = "{}.{}".format(sys.version_info.major, sys.version_info.minor) + start_response("200 OK", [("Content-Type", "text/plain")]) + message = "Test app. From Meinheld with Gunicorn. Using Python {}".format(version) + return [message.encode("utf-8")] diff --git a/tests/test_02_app/simple_app_prestart/app/prestart.sh b/tests/test_02_app/simple_app_prestart/app/prestart.sh new file mode 100644 index 0000000..4fc70af --- /dev/null +++ b/tests/test_02_app/simple_app_prestart/app/prestart.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env sh + +echo "Running custom prestart.sh" diff --git a/tests/test_02_app/simple_app_prestart/latest.dockerfile b/tests/test_02_app/simple_app_prestart/latest.dockerfile new file mode 100644 index 0000000..9d6a046 --- /dev/null +++ b/tests/test_02_app/simple_app_prestart/latest.dockerfile @@ -0,0 +1,2 @@ +FROM tiangolo/meinheld-gunicorn:latest +COPY ./app /app diff --git a/tests/test_02_app/simple_app_prestart/python3.6-alpine3.8.dockerfile b/tests/test_02_app/simple_app_prestart/python3.6-alpine3.8.dockerfile new file mode 100644 index 0000000..4136ee7 --- /dev/null +++ b/tests/test_02_app/simple_app_prestart/python3.6-alpine3.8.dockerfile @@ -0,0 +1,3 @@ +FROM tiangolo/meinheld-gunicorn:python3.6-alpine3.8 + +COPY ./app /app diff --git a/tests/test_02_app/simple_app_prestart/python3.6.dockerfile b/tests/test_02_app/simple_app_prestart/python3.6.dockerfile new file mode 100644 index 0000000..23f1e91 --- /dev/null +++ b/tests/test_02_app/simple_app_prestart/python3.6.dockerfile @@ -0,0 +1,3 @@ +FROM tiangolo/meinheld-gunicorn:python3.6 + +COPY ./app /app diff --git a/tests/test_02_app/simple_app_prestart/python3.7-alpine3.8.dockerfile b/tests/test_02_app/simple_app_prestart/python3.7-alpine3.8.dockerfile new file mode 100644 index 0000000..c802481 --- /dev/null +++ b/tests/test_02_app/simple_app_prestart/python3.7-alpine3.8.dockerfile @@ -0,0 +1,3 @@ +FROM tiangolo/meinheld-gunicorn:python3.7-alpine3.8 + +COPY ./app /app diff --git a/tests/test_02_app/simple_app_prestart/python3.7.dockerfile b/tests/test_02_app/simple_app_prestart/python3.7.dockerfile new file mode 100644 index 0000000..70729c3 --- /dev/null +++ b/tests/test_02_app/simple_app_prestart/python3.7.dockerfile @@ -0,0 +1,3 @@ +FROM tiangolo/meinheld-gunicorn:python3.7 + +COPY ./app /app diff --git a/tests/test_02_app/test_custom_app.py b/tests/test_02_app/test_custom_app.py index 3325fa2..12f8277 100644 --- a/tests/test_02_app/test_custom_app.py +++ b/tests/test_02_app/test_custom_app.py @@ -5,11 +5,33 @@ import pytest import requests -from ..utils import CONTAINER_NAME, IMAGE_NAME, get_config, stop_previous_container +from ..utils import ( + CONTAINER_NAME, + IMAGE_NAME, + get_config, + get_logs, + remove_previous_container, +) client = docker.from_env() +def verify_container(container, response_text): + config_data = get_config(container) + assert config_data["workers_per_core"] == 2 + assert config_data["host"] == "0.0.0.0" + assert config_data["port"] == "80" + assert config_data["loglevel"] == "info" + assert config_data["workers"] > 2 + assert config_data["bind"] == "0.0.0.0:80" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert "Running custom prestart.sh" in logs + response = requests.get("http://127.0.0.1:8000") + assert response.text == response_text + + @pytest.mark.parametrize( "dockerfile,environment,response_text", [ @@ -66,7 +88,7 @@ ], ) def test_custom_app(dockerfile, environment, response_text): - stop_previous_container(client) + remove_previous_container(client) test_path: PurePath = Path(__file__) path = test_path.parent / "custom_app" client.images.build(path=str(path), dockerfile=dockerfile, tag=IMAGE_NAME) @@ -77,15 +99,12 @@ def test_custom_app(dockerfile, environment, response_text): ports={"80": "8000"}, detach=True, ) - config_data = get_config(container) - assert config_data["workers_per_core"] == 2 - assert config_data["host"] == "0.0.0.0" - assert config_data["port"] == "80" - assert config_data["loglevel"] == "info" - assert config_data["workers"] > 2 - assert config_data["bind"] == "0.0.0.0:80" time.sleep(1) - response = requests.get("http://127.0.0.1:8000") - assert response.text == response_text + verify_container(container, response_text) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container, response_text) container.stop() container.remove() diff --git a/tests/test_02_app/test_package_app.py b/tests/test_02_app/test_package_app.py index 3ffb246..07c4624 100644 --- a/tests/test_02_app/test_package_app.py +++ b/tests/test_02_app/test_package_app.py @@ -5,11 +5,35 @@ import pytest import requests -from ..utils import CONTAINER_NAME, IMAGE_NAME, get_config, stop_previous_container +from ..utils import ( + CONTAINER_NAME, + IMAGE_NAME, + get_config, + get_logs, + remove_previous_container, +) client = docker.from_env() +def verify_container(container, response_text): + config_data = get_config(container) + assert config_data["workers_per_core"] == 2 + assert config_data["host"] == "0.0.0.0" + assert config_data["port"] == "80" + assert config_data["loglevel"] == "info" + assert config_data["workers"] > 2 + assert config_data["bind"] == "0.0.0.0:80" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert ( + "Running inside /app/prestart.sh, you could add migrations to this file" in logs + ) + response = requests.get("http://127.0.0.1:8000") + assert response.text == response_text + + @pytest.mark.parametrize( "dockerfile,response_text", [ @@ -21,7 +45,10 @@ "python3.7.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.7", ), - ("latest.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.7"), + ( + "latest.dockerfile", + "Test app. From Meinheld with Gunicorn. Using Python 3.7", + ), ( "python3.6-alpine3.8.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.6", @@ -33,7 +60,7 @@ ], ) def test_package_app(dockerfile, response_text): - stop_previous_container(client) + remove_previous_container(client) test_path: PurePath = Path(__file__) path = test_path.parent / "package_app" client.images.build(path=str(path), dockerfile=dockerfile, tag=IMAGE_NAME) @@ -41,14 +68,11 @@ def test_package_app(dockerfile, response_text): IMAGE_NAME, name=CONTAINER_NAME, ports={"80": "8000"}, detach=True ) time.sleep(1) - config_data = get_config(container) - assert config_data["workers_per_core"] == 2 - assert config_data["host"] == "0.0.0.0" - assert config_data["port"] == "80" - assert config_data["loglevel"] == "info" - assert config_data["workers"] > 2 - assert config_data["bind"] == "0.0.0.0:80" - response = requests.get("http://127.0.0.1:8000") - assert response.text == response_text + verify_container(container, response_text) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container, response_text) container.stop() container.remove() diff --git a/tests/test_02_app/test_package_app_config.py b/tests/test_02_app/test_package_app_config.py index 5113941..2bae026 100644 --- a/tests/test_02_app/test_package_app_config.py +++ b/tests/test_02_app/test_package_app_config.py @@ -10,12 +10,30 @@ IMAGE_NAME, get_config, get_gunicorn_conf_path, - stop_previous_container, + get_logs, + remove_previous_container, ) client = docker.from_env() +def verify_container(container, response_text): + gunicorn_conf_path = get_gunicorn_conf_path(container) + config_data = get_config(container) + assert gunicorn_conf_path == "/app/gunicorn_conf.py" + assert config_data["loglevel"] == "warning" + assert config_data["workers"] == 3 + assert config_data["bind"] == "0.0.0.0:8000" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert ( + "Running inside /app/prestart.sh, you could add migrations to this file" in logs + ) + response = requests.get("http://127.0.0.1:8000") + assert response.text == response_text + + @pytest.mark.parametrize( "dockerfile,response_text", [ @@ -42,7 +60,7 @@ ], ) def test_package_app_config(dockerfile, response_text): - stop_previous_container(client) + remove_previous_container(client) test_path: PurePath = Path(__file__) path = test_path.parent / "package_app_config" client.images.build(path=str(path), dockerfile=dockerfile, tag=IMAGE_NAME) @@ -50,13 +68,11 @@ def test_package_app_config(dockerfile, response_text): IMAGE_NAME, name=CONTAINER_NAME, ports={"8000": "8000"}, detach=True ) time.sleep(1) - gunicorn_conf_path = get_gunicorn_conf_path(container) - config_data = get_config(container) - assert gunicorn_conf_path == "/app/gunicorn_conf.py" - assert config_data["loglevel"] == "warning" - assert config_data["workers"] == 3 - assert config_data["bind"] == "0.0.0.0:8000" - response = requests.get("http://127.0.0.1:8000") - assert response.text == response_text + verify_container(container, response_text) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container, response_text) container.stop() container.remove() diff --git a/tests/test_02_app/test_package_app_custom_config.py b/tests/test_02_app/test_package_app_custom_config.py index ec5f395..a95049c 100644 --- a/tests/test_02_app/test_package_app_custom_config.py +++ b/tests/test_02_app/test_package_app_custom_config.py @@ -5,11 +5,35 @@ import pytest import requests -from ..utils import get_config, get_gunicorn_conf_path, stop_previous_container, CONTAINER_NAME, IMAGE_NAME +from ..utils import ( + CONTAINER_NAME, + IMAGE_NAME, + get_config, + get_gunicorn_conf_path, + get_logs, + remove_previous_container, +) client = docker.from_env() +def verify_container(container, response_text): + gunicorn_conf_path = get_gunicorn_conf_path(container) + config_data = get_config(container) + assert gunicorn_conf_path == "/app/custom_gunicorn_conf.py" + assert config_data["loglevel"] == "warning" + assert config_data["workers"] == 3 + assert config_data["bind"] == "0.0.0.0:8000" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert ( + "Running inside /app/prestart.sh, you could add migrations to this file" in logs + ) + response = requests.get("http://127.0.0.1:8000") + assert response.text == response_text + + @pytest.mark.parametrize( "dockerfile,response_text", [ @@ -21,7 +45,10 @@ "python3.7.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.7", ), - ("latest.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.7"), + ( + "latest.dockerfile", + "Test app. From Meinheld with Gunicorn. Using Python 3.7", + ), ( "python3.6-alpine3.8.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.6", @@ -33,7 +60,7 @@ ], ) def test_package_app_custom_config(dockerfile, response_text): - stop_previous_container(client) + remove_previous_container(client) test_path: PurePath = Path(__file__) path = test_path.parent / "package_app_custom_config" client.images.build(path=str(path), dockerfile=dockerfile, tag=IMAGE_NAME) @@ -45,13 +72,11 @@ def test_package_app_custom_config(dockerfile, response_text): detach=True, ) time.sleep(1) - gunicorn_conf_path = get_gunicorn_conf_path(container) - config_data = get_config(container) - assert gunicorn_conf_path == "/app/custom_gunicorn_conf.py" - assert config_data["loglevel"] == "warning" - assert config_data["workers"] == 3 - assert config_data["bind"] == "0.0.0.0:8000" - response = requests.get("http://127.0.0.1:8000") - assert response.text == response_text + verify_container(container, response_text) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container, response_text) container.stop() container.remove() diff --git a/tests/test_02_app/test_package_app_sub_config.py b/tests/test_02_app/test_package_app_sub_config.py index ec4ba24..6208d00 100644 --- a/tests/test_02_app/test_package_app_sub_config.py +++ b/tests/test_02_app/test_package_app_sub_config.py @@ -10,12 +10,30 @@ IMAGE_NAME, get_config, get_gunicorn_conf_path, - stop_previous_container, + get_logs, + remove_previous_container, ) client = docker.from_env() +def verify_container(container, response_text): + gunicorn_conf_path = get_gunicorn_conf_path(container) + config_data = get_config(container) + assert gunicorn_conf_path == "/app/app/gunicorn_conf.py" + assert config_data["loglevel"] == "warning" + assert config_data["workers"] == 3 + assert config_data["bind"] == "0.0.0.0:8000" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert ( + "Running inside /app/prestart.sh, you could add migrations to this file" in logs + ) + response = requests.get("http://127.0.0.1:8000") + assert response.text == response_text + + @pytest.mark.parametrize( "dockerfile,response_text", [ @@ -27,7 +45,10 @@ "python3.7.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.7", ), - ("latest.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.7"), + ( + "latest.dockerfile", + "Test app. From Meinheld with Gunicorn. Using Python 3.7", + ), ( "python3.6-alpine3.8.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.6", @@ -39,7 +60,7 @@ ], ) def test_package_app_sub_config(dockerfile, response_text): - stop_previous_container(client) + remove_previous_container(client) test_path: PurePath = Path(__file__) path = test_path.parent / "package_app_sub_config" client.images.build(path=str(path), dockerfile=dockerfile, tag=IMAGE_NAME) @@ -47,13 +68,11 @@ def test_package_app_sub_config(dockerfile, response_text): IMAGE_NAME, name=CONTAINER_NAME, ports={"8000": "8000"}, detach=True ) time.sleep(1) - gunicorn_conf_path = get_gunicorn_conf_path(container) - config_data = get_config(container) - assert gunicorn_conf_path == "/app/app/gunicorn_conf.py" - assert config_data["loglevel"] == "warning" - assert config_data["workers"] == 3 - assert config_data["bind"] == "0.0.0.0:8000" - response = requests.get("http://127.0.0.1:8000") - assert response.text == response_text + verify_container(container, response_text) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container, response_text) container.stop() container.remove() diff --git a/tests/test_02_app/test_simple_app.py b/tests/test_02_app/test_simple_app.py index df04686..33fca06 100644 --- a/tests/test_02_app/test_simple_app.py +++ b/tests/test_02_app/test_simple_app.py @@ -5,11 +5,35 @@ import pytest import requests -from ..utils import CONTAINER_NAME, IMAGE_NAME, get_config, stop_previous_container +from ..utils import ( + CONTAINER_NAME, + IMAGE_NAME, + get_config, + get_logs, + remove_previous_container, +) client = docker.from_env() +def verify_container(container, response_text): + config_data = get_config(container) + assert config_data["workers_per_core"] == 2 + assert config_data["host"] == "0.0.0.0" + assert config_data["port"] == "80" + assert config_data["loglevel"] == "info" + assert config_data["workers"] > 2 + assert config_data["bind"] == "0.0.0.0:80" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert ( + "Running inside /app/prestart.sh, you could add migrations to this file" in logs + ) + response = requests.get("http://127.0.0.1:8000") + assert response.text == response_text + + @pytest.mark.parametrize( "dockerfile,response_text", [ @@ -21,7 +45,10 @@ "python3.7.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.7", ), - ("latest.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.7"), + ( + "latest.dockerfile", + "Test app. From Meinheld with Gunicorn. Using Python 3.7", + ), ( "python3.6-alpine3.8.dockerfile", "Test app. From Meinheld with Gunicorn. Using Python 3.6", @@ -33,7 +60,7 @@ ], ) def test_simple_app(dockerfile, response_text): - stop_previous_container(client) + remove_previous_container(client) IMAGE_NAME test_path: PurePath = Path(__file__) path = test_path.parent / "simple_app" @@ -42,14 +69,11 @@ def test_simple_app(dockerfile, response_text): IMAGE_NAME, name=CONTAINER_NAME, ports={"80": "8000"}, detach=True ) time.sleep(1) - config_data = get_config(container) - assert config_data["workers_per_core"] == 2 - assert config_data["host"] == "0.0.0.0" - assert config_data["port"] == "80" - assert config_data["loglevel"] == "info" - assert config_data["workers"] > 2 - assert config_data["bind"] == "0.0.0.0:80" - response = requests.get("http://127.0.0.1:8000") - assert response.text == response_text + verify_container(container, response_text) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container, response_text) container.stop() container.remove() diff --git a/tests/test_02_app/test_simple_app_prestart.py b/tests/test_02_app/test_simple_app_prestart.py new file mode 100644 index 0000000..561eb1c --- /dev/null +++ b/tests/test_02_app/test_simple_app_prestart.py @@ -0,0 +1,77 @@ +import time +from pathlib import Path, PurePath + +import docker +import pytest +import requests + +from ..utils import ( + CONTAINER_NAME, + IMAGE_NAME, + get_config, + get_logs, + remove_previous_container, +) + +client = docker.from_env() + + +def verify_container(container, response_text): + config_data = get_config(container) + assert config_data["workers_per_core"] == 2 + assert config_data["host"] == "0.0.0.0" + assert config_data["port"] == "80" + assert config_data["loglevel"] == "info" + assert config_data["workers"] > 2 + assert config_data["bind"] == "0.0.0.0:80" + logs = get_logs(container) + assert "Checking for script in /app/prestart.sh" in logs + assert "Running script /app/prestart.sh" in logs + assert "Running custom prestart.sh" in logs + response = requests.get("http://127.0.0.1:8000") + assert response.text == response_text + + +@pytest.mark.parametrize( + "dockerfile,response_text", + [ + ( + "python3.6.dockerfile", + "Test app. From Meinheld with Gunicorn. Using Python 3.6", + ), + ( + "python3.7.dockerfile", + "Test app. From Meinheld with Gunicorn. Using Python 3.7", + ), + ( + "latest.dockerfile", + "Test app. From Meinheld with Gunicorn. Using Python 3.7", + ), + ( + "python3.6-alpine3.8.dockerfile", + "Test app. From Meinheld with Gunicorn. Using Python 3.6", + ), + ( + "python3.7-alpine3.8.dockerfile", + "Test app. From Meinheld with Gunicorn. Using Python 3.7", + ), + ], +) +def test_simple_app(dockerfile, response_text): + remove_previous_container(client) + IMAGE_NAME + test_path: PurePath = Path(__file__) + path = test_path.parent / "simple_app_prestart" + client.images.build(path=str(path), dockerfile=dockerfile, tag=IMAGE_NAME) + container = client.containers.run( + IMAGE_NAME, name=CONTAINER_NAME, ports={"80": "8000"}, detach=True + ) + time.sleep(1) + verify_container(container, response_text) + container.stop() + # Test that everything works after restarting too + container.start() + time.sleep(1) + verify_container(container, response_text) + container.stop() + container.remove() diff --git a/tests/utils.py b/tests/utils.py index ca17310..1bdeb66 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -27,10 +27,15 @@ def get_config(container): return json.loads(result.output.decode()) -def stop_previous_container(client): +def remove_previous_container(client): try: previous = client.containers.get(CONTAINER_NAME) previous.stop() previous.remove() except NotFound: return None + + +def get_logs(container): + logs: str = container.logs() + return logs.decode("utf-8")