From e82f8a6e6a75d01f61fc1cb2a8796c71efca75eb Mon Sep 17 00:00:00 2001 From: Atul Varma Date: Wed, 30 Jun 2021 23:18:08 -0400 Subject: [PATCH] Add a Docker Compose setup for development (#128) * Add a Docker Compose setup. * Make app and docs ports configurable. * Add .dockerignore. * Wait for postgres in app container. * Run app container in /app. * Add docs to contributing. * Add settings.LOGIN_URL. * Make GID, UID customizable and document it. * Move LOGIN_URL to settings.py. * Add redirect from / to /dashboard/. * Auto-run migrations on 'docker compose up'. Thanks, Atul Varma --- .dockerignore | 10 +++ Dockerfile | 30 +++++++ docker-compose.yml | 34 ++++++++ docs/contributing.md | 95 +++++++++++++++++++++ test_project/config/settings.py | 2 + test_project/config/settings_interactive.py | 12 +++ test_project/config/urls.py | 3 + test_project/test_dashboard_permissions.py | 2 +- test_project/wait-for-postgres.sh | 11 +++ 9 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 test_project/config/settings_interactive.py create mode 100755 test_project/wait-for-postgres.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d67eeb9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.venv +.vscode +__pycache__/ +*.py[cod] +*$py.class +venv +.eggs +.pytest_cache +*.egg-info +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f526e28 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM python:3.9 + +WORKDIR /app + +# Set up the minimum structure needed to install +# django_sql_dashboard's dependencies and the package itself +# in development mode. +COPY setup.py README.md . +RUN mkdir django_sql_dashboard && pip install -e '.[test]' + +# We need to have postgres installed in this container +# because the automated test suite actually spins up +# (and shuts down) a database inside the container. +RUN apt-get update && apt-get install -y \ + postgresql postgresql-contrib \ + && rm -rf /var/lib/apt/lists/* + +# Install dependencies needed for editing documentation. +COPY docs/requirements.txt . +RUN pip install -r requirements.txt + +ARG GID=1000 +ARG UID=1000 + +# Set up a non-root user. Aside from being best practice, +# we also need to do this because the test suite refuses to +# run as the root user. +RUN groupadd -g ${GID} appuser && useradd -r -u ${UID} -g appuser appuser + +USER appuser diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1090668 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +version: "2" +services: + app: + build: . + links: + - db + volumes: + - .:/app + environment: + - DATABASE_URL=postgres://appuser:test123@db/test_project + - DJANGO_SETTINGS_MODULE=config.settings_interactive + - PYTHONUNBUFFERED=yup + working_dir: /app + entrypoint: ["./test_project/wait-for-postgres.sh"] + ports: + - "${APP_PORT:-8000}:${APP_PORT:-8000}" + command: bash -c "python test_project/manage.py migrate && python test_project/manage.py runserver 0.0.0.0:${APP_PORT:-8000}" + docs: + build: . + volumes: + - .:/app + working_dir: /app/docs + ports: + - "${DOCS_PORT:-8001}:${DOCS_PORT:-8001}" + command: make SPHINXOPTS="--host 0.0.0.0 --port ${DOCS_PORT:-8001}" livehtml + db: + # Note that this database is only used when we use + # test_project interactively; automated tests spin up + # their own database inside the app container. + image: postgres:13-alpine + environment: + - POSTGRES_PASSWORD=test123 + - POSTGRES_USER=appuser + - POSTGRES_DB=test_project diff --git a/docs/contributing.md b/docs/contributing.md index faaaf02..ae244bb 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -42,3 +42,98 @@ To build the documentation locally, run the following: make livehtml This will start a live preview server, using [sphinx-autobuild](https://pypi.org/project/sphinx-autobuild/). + +## Using Docker Compose + +If you're familiar with Docker--or even if you're not--you may want to consider using our optional Docker Compose setup. + +An advantage of this approach is that it relieves you of setting up any dependencies, such as ensuring that you have the proper version of Python and Postgres and so forth. On the downside, however, it does require you to familiarize yourself with Docker, which, while relatively easy to use, still has its own learning curve. + +To try out the Docker Compose setup, you will first want to [get Docker][] and [install Docker Compose][]. + +Then, after checking out the code, run the following: + +``` +cd django-sql-dashboard +docker-compose build +``` + +At this point, you can start editing code. To run any development tools such as `pytest` or `black`, just prefix everything with `docker-compose run app`. For instance, to run the test suite, run: + +``` +docker-compose run app python pytest +``` + +If this is a hassle, you can instead run a bash shell inside your container: + +``` +docker-compose run app bash +``` + +At this point, you'll be in a bash shell inside your container, and can run development tools directly. + +[get Docker]: https://docs.docker.com/get-docker/ +[install Docker Compose]: https://docs.docker.com/compose/install/ + +### Using the dashboard interactively + +The Docker Compose setup is configured to run a simple test project that you can use to tinker with the dashboard interactively. + +To use it, run: + +``` +docker-compose up +``` + +Then, in a separate terminal, run: + +``` +docker-compose run app python test_project/manage.py createsuperuser +``` + +You will now be prompted to enter details about a new superuser. Once you've done that, you can visit the example app's dashboard at http://localhost:8000/. After entering the credentials for the superuser you just created, you will be able to tinker with the dashboard. + +### Editing the documentation + +Running `docker-compose up` also starts the documentation system's live preview server. You can visit it at http://localhost:8001/. + +### Changing the default ports + +If you are already using ports 8000 and/or 8001 for other things, you can change them. To do this, create a file in the repository root called `.env` and populate it with the following: + +``` +APP_PORT=9000 +DOCS_PORT=9001 +``` + +You can change the above port values to whatever makes sense for your setup. + +Once you next run `docker-compose up` again, the services will be running on the ports you specified in `.env`. + +### Changing the default UID and GID + +The default settings assume that the user id (UID) and group id (GID) of the account you're using to develop are both 1000. This is likely to be the case, since that's the UID/GID of the first non-root account on most systems. However, if your account doesn't match this, you can customize the container to use a different UID/GID. + +For instance, if your UID and GID are 1001, you can build your container with the following arguments: + +``` +docker-compose build --build-arg UID=1001 --build-arg GID=1001 +``` + +### Updating + +The project's Python dependencies are all baked into the container image, which means that whenever they change (or to be safe, whenever you `git pull` new changes to the codebase), you will want to run: + +``` +docker-compose build +``` + +You will also want to restart `docker-compose up`. + +### Cleaning up + +If you somehow get your Docker Compose setup into a broken state, or you decide that you never use Docker Compose again, you can clean everything up by running: + +``` +docker-compose down -v +``` diff --git a/test_project/config/settings.py b/test_project/config/settings.py index 5bd24cf..1c65bbc 100644 --- a/test_project/config/settings.py +++ b/test_project/config/settings.py @@ -97,3 +97,5 @@ # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = "/static/" + +LOGIN_URL = "/admin/login/" diff --git a/test_project/config/settings_interactive.py b/test_project/config/settings_interactive.py new file mode 100644 index 0000000..bb78b06 --- /dev/null +++ b/test_project/config/settings_interactive.py @@ -0,0 +1,12 @@ +# Normally test_project is used as scaffolding for +# django_sql_dashboard's automated tests. However, it can +# be useful during development to have a sample project to +# tinker with interactively. These Django settings can be +# useful when we want to do that. + +from .settings import * + +# Just have our dashboard use the exact same credentials for +# our database, there's no need to bother with read-only +# permissions when using test_project interactively. +DATABASES["dashboard"] = DATABASES["default"] diff --git a/test_project/config/urls.py b/test_project/config/urls.py index a06d49f..b781c44 100644 --- a/test_project/config/urls.py +++ b/test_project/config/urls.py @@ -1,9 +1,12 @@ from django.contrib import admin from django.urls import include, path +from django.views.generic.base import RedirectView import django_sql_dashboard + urlpatterns = [ path("dashboard/", include(django_sql_dashboard.urls)), path("admin/", admin.site.urls), + path("", RedirectView.as_view(url="/dashboard/")), ] diff --git a/test_project/test_dashboard_permissions.py b/test_project/test_dashboard_permissions.py index 19aa172..8f9d1c9 100644 --- a/test_project/test_dashboard_permissions.py +++ b/test_project/test_dashboard_permissions.py @@ -10,7 +10,7 @@ def test_anonymous_user_redirected_to_login(client): response = client.get("/dashboard/?sql=select+1") assert response.status_code == 302 - assert response.url == "/accounts/login/?next=/dashboard/%3Fsql%3Dselect%2B1" + assert response.url == "/admin/login/?next=/dashboard/%3Fsql%3Dselect%2B1" def test_superusers_allowed(admin_client, dashboard_db): diff --git a/test_project/wait-for-postgres.sh b/test_project/wait-for-postgres.sh new file mode 100755 index 0000000..06e79c1 --- /dev/null +++ b/test_project/wait-for-postgres.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# wait-for-postgres.sh + +set -e + +until psql $DATABASE_URL -c '\q'; do + >&2 echo "Postgres is unavailable - sleeping" + sleep 1 +done + +exec "$@"