diff options
author | 2019-09-28 06:20:07 +1000 | |
---|---|---|
committer | 2019-09-28 06:20:07 +1000 | |
commit | 4769c8b5e3ba70301b1123c5750429b2092b01b1 (patch) | |
tree | 172f46a1b495a3b6948e183165a3e623b1462380 | |
parent | Ensure docker containers are published only for master branch bu… (#264) (diff) |
Create custom manage.py entry point, remove scripts and merge Dockerfile.
-rw-r--r-- | .dockerignore | 5 | ||||
-rw-r--r-- | .flake8 | 2 | ||||
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Pipfile | 5 | ||||
-rw-r--r-- | Pipfile.lock | 68 | ||||
-rw-r--r-- | docker-compose.yml | 5 | ||||
-rw-r--r-- | docker/Dockerfile (renamed from docker/app/Dockerfile) | 11 | ||||
-rw-r--r-- | docker/app/build-wiki.Dockerfile | 2 | ||||
-rw-r--r-- | docker/app/local.Dockerfile | 28 | ||||
-rw-r--r-- | docker/app/scripts/build-wiki.sh | 4 | ||||
-rwxr-xr-x | docker/app/scripts/migrate.sh | 10 | ||||
-rwxr-xr-x | docker/app/scripts/migrate_and_serve.sh | 21 | ||||
-rw-r--r-- | docker/uwsgi.ini (renamed from docker/app/uwsgi.ini) | 0 | ||||
-rw-r--r-- | docker/wheels/wiki-0.5.dev20190420204942-py3-none-any.whl (renamed from docker/app/wheels/wiki-0.5.dev20190420204942-py3-none-any.whl) | bin | 1287002 -> 1287002 bytes | |||
-rwxr-xr-x | manage.py | 140 |
15 files changed, 197 insertions, 106 deletions
diff --git a/.dockerignore b/.dockerignore index 771913a3..236295ca 100644 --- a/.dockerignore +++ b/.dockerignore @@ -17,9 +17,8 @@ pydis_site/apps/staff/tests CHANGELOG.md CONTRIBUTING.md docker -!docker/app/scripts/migrate.sh -!docker/app/uwsgi.ini -!docker/app/wheels +!docker/uwsgi.ini +!docker/wheels docker-compose.yml Dockerfile.local docs @@ -3,7 +3,7 @@ max-line-length=100 docstring-convention=all import-order-style=pycharm application_import_names=pydis_site -exclude=__pycache__, venv, .venv, manage.py, **/migrations +exclude=__pycache__, venv, .venv, **/migrations ignore= B311,W503,E226,S311,T000 # Missing Docstrings @@ -117,7 +117,7 @@ media/ pip-wheel-metadata/ staticfiles/ -!docker/app/wheels +!docker/wheels *.js.tmp log.* @@ -18,8 +18,9 @@ whitenoise = "==4.1.2" requests = "~=2.21" pygments = "~=2.3.1" #wiki = {git = "https://github.com/python-discord/django-wiki.git"} -wiki = {path = "./docker/app/wheels/wiki-0.5.dev20190420204942-py3-none-any.whl"} -pyyaml = "*" +wiki = {path = "./docker/wheels/wiki-0.5.dev20190420204942-py3-none-any.whl"} +pyyaml = "~=5.1" +pyuwsgi = "~=2.0" [dev-packages] coverage = "~=4.5.3" diff --git a/Pipfile.lock b/Pipfile.lock index 01345754..5339ad58 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d0e378fcea422eb28537a91e60e7faab8ae08a4ad9e3f1bd71fe89d377d36a8d" + "sha256": "ec1cd3daf99e1b368137579c284c79b3dfe5c6eb151758aee102045fddcd09e4" }, "pipfile-spec": 6, "requires": { @@ -262,6 +262,36 @@ ], "version": "==2019.2" }, + "pyuwsgi": { + "hashes": [ + "sha256:15a4626740753b0d0dfeeac7d367f9b2e89ab6af16c195927e60f75359fc1bbc", + "sha256:24c40c3b889eb9f283d43feffbc0f7c7fc024e914451425156ddb68af3df1e71", + "sha256:393737bd43a7e38f0a4a1601a37a69c4bf893635b37665ff958170fdb604fdb7", + "sha256:5a08308f87e639573c1efaa5966a6d04410cd45a73c4586a932fe3ee4b56369d", + "sha256:5f4b36c0dbb9931c4da8008aa423158be596e3b4a23cec95a958631603a94e45", + "sha256:7c31794f71bbd0ccf542cab6bddf38aa69e84e31ae0f9657a2e18ebdc150c01a", + "sha256:802ec6dad4b6707b934370926ec1866603abe31ba03c472f56149001b3533ba1", + "sha256:814d73d4569add69a6c19bb4a27cd5adb72b196e5e080caed17dbda740402072", + "sha256:829299cd117cf8abe837796bf587e61ce6bfe18423a3a1c510c21e9825789c2c", + "sha256:85f2210ceae5f48b7d8fad2240d831f4b890cac85cd98ca82683ac6aa481dfc8", + "sha256:861c94442b28cd64af033e88e0f63c66dbd5609f67952dc18694098b47a43f3a", + "sha256:957bc6316ffc8463795d56d9953d58e7f32aa5aad1c5ac80bc45c69f3299961e", + "sha256:9760c3f56fb5f15852d163429096600906478e9ed2c189a52f2bb21d8a2a986c", + "sha256:a4b24703ea818196d0be1dc64b3b57b79c67e8dee0cfa207a4216220912035a7", + "sha256:ad7f4968c1ddbf139a306d9b075360d959cc554d994ba5e1f512af9a40e62357", + "sha256:b1127d34b90f74faf1707718c57a4193ac028b9f4aec0238638983132297d456", + "sha256:bcb04d6ec644b3e08d03c64851e06edd7110489261e50627a4bcadf66ff6920e", + "sha256:bebfebb9ee83d7cf37668bf54275b677b7ae283e84a944f9f3ac6a4b66f95d4b", + "sha256:c29892dafc65a8b6eb95823fa4bac7754ca3fd1c28ab8d2a973289531b340a27", + "sha256:cb296b50b51ba022b0090b28d032ff1dd395a6db03672b65a39e83532edad527", + "sha256:ce777ebdf49ce736fc04abf555b5c41ab3f130127543a689dcf8d4871cd18fe4", + "sha256:d8b4bf930b6a19bc9ee982b9163d948c87501ad91b71516924e8ed25fe85d2ee", + "sha256:e2a420f2c4d35f3ec0b7e752a80d7bd385e2c5a64f67c05f2d2d74230e3114b6", + "sha256:fed899ce96f4f2b4d1b9f338dd145a4040ee1d8a5152213af0dd8d4a4d36e9fe" + ], + "index": "pypi", + "version": "==2.0.18.post0" + }, "pyyaml": { "hashes": [ "sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", @@ -336,7 +366,7 @@ "hashes": [ "sha256:73a53bc770ce6b1d2ea6916d81ccefe40751d87b30556fa3b992c85b7fde8534" ], - "path": "./docker/app/wheels/wiki-0.5.dev20190420204942-py3-none-any.whl", + "path": "./docker/wheels/wiki-0.5.dev20190420204942-py3-none-any.whl", "version": "==0.5.dev20190420204942" } }, @@ -369,14 +399,6 @@ ], "version": "==2.0.1" }, - "colorama": { - "hashes": [ - "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", - "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" - ], - "markers": "platform_system == 'Windows'", - "version": "==0.4.1" - }, "coverage": { "hashes": [ "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", @@ -432,11 +454,11 @@ }, "flake8-annotations": { "hashes": [ - "sha256:1309f2bc9853a2d77d578b089d331b0b832b40c97932641e136e1b49d3650c82", - "sha256:3ecdd27054c3eed6484139025698465e3c9f4e68dbd5043d0204fcb2550ee27b" + "sha256:6ac7ca1e706307686b60af8043ff1db31dc2cfc1233c8210d67a3d9b8f364736", + "sha256:b51131007000d67217608fa028a35ff80aa400b474e5972f1f99c2cf9d26bd2e" ], "index": "pypi", - "version": "==1.0.0" + "version": "==1.1.0" }, "flake8-bandit": { "hashes": [ @@ -646,6 +668,26 @@ ], "version": "==0.10.0" }, + "typed-ast": { + "hashes": [ + "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", + "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", + "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", + "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", + "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", + "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", + "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", + "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", + "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", + "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", + "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", + "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", + "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", + "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", + "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" + ], + "version": "==1.4.0" + }, "unittest-xml-reporting": { "hashes": [ "sha256:140982e4b58e4052d9ecb775525b246a96bfc1fc26097806e05ea06e9166dd6c", diff --git a/docker-compose.yml b/docker-compose.yml index 4ea110c4..71a5593f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,8 +22,8 @@ services: web: build: context: . - dockerfile: docker/app/local.Dockerfile - command: docker/app/scripts/migrate_and_serve.sh + dockerfile: docker/Dockerfile + command: ["run", "--debug"] ports: - "127.0.0.1:8000:8000" depends_on: @@ -33,7 +33,6 @@ services: - staticfiles:/var/www/static environment: DATABASE_URL: postgres://pysite:pysite@postgres:5432/pysite - DEBUG: "true" SECRET_KEY: suitable-for-development-only STATIC_ROOT: /var/www/static diff --git a/docker/app/Dockerfile b/docker/Dockerfile index a6986fb2..aa427947 100644 --- a/docker/app/Dockerfile +++ b/docker/Dockerfile @@ -11,8 +11,8 @@ ENV PIP_NO_CACHE_DIR=false \ # Create non-root user. RUN useradd --system --shell /bin/false --uid 1500 pysite -# Install pipenv & pyuwsgi -RUN pip install -U pipenv pyuwsgi +# Install pipenv +RUN pip install -U pipenv # Copy the project files into working directory WORKDIR /app @@ -21,7 +21,6 @@ COPY . . # Install project dependencies RUN pipenv install --system --deploy -# Migrate, collect and start the app -RUN chmod +x /app/docker/app/scripts/migrate.sh -ENTRYPOINT ["/app/docker/app/scripts/migrate.sh"] -CMD ["uwsgi", "--ini", "docker/app/uwsgi.ini"] +# Run web server through custom manager +ENTRYPOINT ["python", "manage.py"] +CMD ["run"] diff --git a/docker/app/build-wiki.Dockerfile b/docker/app/build-wiki.Dockerfile deleted file mode 100644 index 92003377..00000000 --- a/docker/app/build-wiki.Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM python:3.7 -RUN pip --no-cache-dir wheel --wheel-dir=/wheels "wiki @ git+https://github.com/python-discord/django-wiki.git" diff --git a/docker/app/local.Dockerfile b/docker/app/local.Dockerfile deleted file mode 100644 index 9e15c438..00000000 --- a/docker/app/local.Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -FROM python:3.7-slim - -# Allow service to handle stops gracefully -STOPSIGNAL SIGQUIT - -# Set pip to have cleaner logs and no saved cache -ENV PIP_NO_CACHE_DIR=false \ - PIPENV_HIDE_EMOJIS=1 \ - PIPENV_NOSPIN=1 - -# Create non-root user -RUN useradd --system --shell /bin/false --uid 1500 pysite - -# Install pipenv & pyuwsgi -RUN pip install -U pipenv pyuwsgi - -# Copy the project files into working directory -WORKDIR /app -COPY . . - -# Install project dependencies -RUN pipenv install --system --deploy - -# Prepare static files for site -RUN SECRET_KEY=placeholder DATABASE_URL=sqlite:// \ - python3 manage.py collectstatic --no-input --clear --verbosity 0 - -CMD ["uwsgi", "--ini", "docker/app/uwsgi.ini"] diff --git a/docker/app/scripts/build-wiki.sh b/docker/app/scripts/build-wiki.sh deleted file mode 100644 index 07c54f66..00000000 --- a/docker/app/scripts/build-wiki.sh +++ /dev/null @@ -1,4 +0,0 @@ -docker build -t build_uwsgi -f docker/app/build-wiki.Dockerfile . -CONTAINER=$(docker run -itd build_uwsgi /bin/bash) -docker cp "$CONTAINER:/wheels" docker/app -docker stop "$CONTAINER" diff --git a/docker/app/scripts/migrate.sh b/docker/app/scripts/migrate.sh deleted file mode 100755 index 22636c93..00000000 --- a/docker/app/scripts/migrate.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -echo --- Applying migrations --- -python manage.py migrate --verbosity 1 - -echo --- Collecting static files --- -python manage.py collectstatic --no-input --clear --verbosity 1 - -echo --- Starting uwsgi --- -exec "$@" # This runs the CMD at the end of the Dockerfile diff --git a/docker/app/scripts/migrate_and_serve.sh b/docker/app/scripts/migrate_and_serve.sh deleted file mode 100755 index c30d7e04..00000000 --- a/docker/app/scripts/migrate_and_serve.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -set -eu - -### NOTE -# This file is intended to be used by local setups. -# You do not want to run the Django development server -# in production. The default Dockerfile command will -# run using uWSGI, this script is provided purely as -# a convenience to run migrations and start a development server. - -echo [i] Applying migrations. -python manage.py migrate --verbosity 1 - -echo [i] Collecting static files. -python manage.py collectstatic --no-input --clear --verbosity 1 - -echo [i] Creating a superuser. -echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('admin', 'admin', 'admin') if not User.objects.filter(username='admin').exists() else print('Admin user already exists')" | python manage.py shell - -echo [i] Starting server. -python manage.py runserver 0.0.0.0:8000 diff --git a/docker/app/uwsgi.ini b/docker/uwsgi.ini index 3f35258c..3f35258c 100644 --- a/docker/app/uwsgi.ini +++ b/docker/uwsgi.ini diff --git a/docker/app/wheels/wiki-0.5.dev20190420204942-py3-none-any.whl b/docker/wheels/wiki-0.5.dev20190420204942-py3-none-any.whl Binary files differindex b7637e76..b7637e76 100644 --- a/docker/app/wheels/wiki-0.5.dev20190420204942-py3-none-any.whl +++ b/docker/wheels/wiki-0.5.dev20190420204942-py3-none-any.whl @@ -1,21 +1,137 @@ #!/usr/bin/env python import os +import re +import socket import sys +import time +from typing import List +import django +import pyuwsgi +from django.contrib.auth import get_user_model +from django.core.management import call_command, execute_from_command_line -# Separate definition to ease calling this in other scripts. -def main(): + +DEFAULT_ENVS = { + "DJANGO_SETTINGS_MODULE": "pydis_site.settings", + "SUPER_USERNAME": "admin", + "SUPER_PASSWORD": "admin" +} + + +for key, value in DEFAULT_ENVS.items(): + os.environ.setdefault(key, value) + + +class SiteManager: + """ + Manages the preparation and serving of the website. + + Handles both development and production environments. + + Usage: + manage.py run [option]... + + Options: + --debug Runs a development server with debug mode enabled. + --silent Sets no output in console for preparation commands. + --verbose Sets verbose output for preparation commands. + """ + + def __init__(self, args: List[str]): + self.debug = "--debug" in args + self.silent = "--silent" in args + + if self.silent: + self.verbosity = 0 + else: + self.verbosity = 2 if "--verbose" in args else 1 + + if self.verbosity and self.debug: + os.environ.setdefault("DEBUG", "true") + print("Starting in debug mode.") + + @staticmethod + def create_superuser() -> None: + """Create a default django admin super user in development environments.""" + print("Creating a superuser.") + + name = os.environ["SUPER_USERNAME"] + password = os.environ["SUPER_PASSWORD"] + user = get_user_model() + + if user.objects.filter(username=name).exists(): + return print('Admin superuser already exists') + + user.objects.create_superuser(name, '', password) + + @staticmethod + def wait_for_postgres() -> None: + """Wait for the PostgreSQL database specified in DATABASE_URL.""" + print("Waiting for PostgreSQL database.") + + # Get database URL based on environmental variable passed in compose + database_url = os.environ["DATABASE_URL"] + match = re.search(r"@(\w+):(\d+)/", database_url) + if not match: + raise OSError("Valid DATABASE_URL environmental variable not found.") + domain = match.group(1) + port = int(match.group(2)) + + # Attempt to connect to the database socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + while True: + try: + # Ignore 'incomplete startup packet' + s.connect((domain, port)) + s.shutdown(socket.SHUT_RDWR) + print("Database is ready.") + break + except socket.error: + print("Not ready yet, retrying.") + time.sleep(0.5) + + def prepare_server(self) -> None: + """Perform preparation tasks before running the server.""" + django.setup() + + if self.debug: + self.wait_for_postgres() + self.create_superuser() + + print("Applying migrations.") + call_command("migrate", verbosity=self.verbosity) + print("Collecting static files.") + call_command("collectstatic", interactive=False, clear=True, verbosity=self.verbosity) + + def run_server(self) -> None: + """Prepare and run the web server.""" + in_reloader = os.environ.get('RUN_MAIN') == 'true' + + # Prevent preparing twice when in debug mode due to reloader + if not self.debug or in_reloader: + self.prepare_server() + + print("Starting server.") + + # Create a superuser and run the development server + if self.debug: + call_command("runserver", "0.0.0.0:8000") + return + + # Run uwsgi for production server + pyuwsgi.run(["--ini", "docker/uwsgi.ini"]) + + +def main() -> None: """Entry point for Django management script.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pydis_site.settings') - try: - from django.core.management import execute_from_command_line - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc - execute_from_command_line(sys.argv) + # Use the custom site manager for launching the server + if len(sys.argv) > 1 and sys.argv[1] == "run": + SiteManager(sys.argv).run_server() + + # Pass any others directly to standard management commands + else: + execute_from_command_line(sys.argv) if __name__ == '__main__': |