From 4769c8b5e3ba70301b1123c5750429b2092b01b1 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sat, 28 Sep 2019 06:20:07 +1000 Subject: Create custom manage.py entry point, remove scripts and merge Dockerfile. --- manage.py | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 128 insertions(+), 12 deletions(-) (limited to 'manage.py') diff --git a/manage.py b/manage.py index b257ea65..8838b810 100755 --- a/manage.py +++ b/manage.py @@ -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__': -- cgit v1.2.3 From 6d9dcc30f8b73aff8d0f663ddedd28b5642a998d Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sat, 28 Sep 2019 07:17:17 +1000 Subject: Automatically create a default bot api token for dev. --- manage.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'manage.py') diff --git a/manage.py b/manage.py index 8838b810..411e74d7 100755 --- a/manage.py +++ b/manage.py @@ -15,7 +15,8 @@ from django.core.management import call_command, execute_from_command_line DEFAULT_ENVS = { "DJANGO_SETTINGS_MODULE": "pydis_site.settings", "SUPER_USERNAME": "admin", - "SUPER_PASSWORD": "admin" + "SUPER_PASSWORD": "admin", + "DEFAULT_BOT_API_KEY": "badbot13m0n8f570f942013fc818f234916ca531", } @@ -34,8 +35,8 @@ class SiteManager: 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. + --silent Sets minimal console output. + --verbose Sets verbose console output. """ def __init__(self, args: List[str]): @@ -58,12 +59,27 @@ class SiteManager: name = os.environ["SUPER_USERNAME"] password = os.environ["SUPER_PASSWORD"] + bot_token = os.environ["DEFAULT_BOT_API_KEY"] user = get_user_model() + # Get or create admin superuser. if user.objects.filter(username=name).exists(): - return print('Admin superuser already exists') - - user.objects.create_superuser(name, '', password) + user = user.objects.get(username=name) + print('Admin superuser already exists.') + else: + user = user.objects.create_superuser(name, '', password) + print('Admin superuser created.') + + # Setup a default bot token to connect with site API + from rest_framework.authtoken.models import Token + token, is_new = Token.objects.update_or_create(user=user) + if token.key != bot_token: + token.delete() + token, is_new = Token.objects.update_or_create(user=user, key=bot_token) + if is_new: + print(f"New bot token created: {token}") + else: + print(f"Existing bot token found: {token}") @staticmethod def wait_for_postgres() -> None: -- cgit v1.2.3 From 2522472de9a7b8fc8b24476bc047fb6d331a14e2 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sat, 28 Sep 2019 22:56:35 +1000 Subject: Fix comments after previous changes. --- manage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'manage.py') diff --git a/manage.py b/manage.py index 411e74d7..13a8d883 100755 --- a/manage.py +++ b/manage.py @@ -124,13 +124,13 @@ class SiteManager: """Prepare and run the web server.""" in_reloader = os.environ.get('RUN_MAIN') == 'true' - # Prevent preparing twice when in debug mode due to reloader + # Prevent preparing twice when in dev 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 + # Run the development server if self.debug: call_command("runserver", "0.0.0.0:8000") return -- cgit v1.2.3 From a399d04559c23f4f2ddfd57e1a8692a8fe065c97 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 29 Sep 2019 02:53:40 +1000 Subject: Remove useless verbosity check. --- manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'manage.py') diff --git a/manage.py b/manage.py index 13a8d883..baf7333a 100755 --- a/manage.py +++ b/manage.py @@ -48,7 +48,7 @@ class SiteManager: else: self.verbosity = 2 if "--verbose" in args else 1 - if self.verbosity and self.debug: + if self.debug: os.environ.setdefault("DEBUG", "true") print("Starting in debug mode.") -- cgit v1.2.3 From d6ad156061f175213122dc4dfbf2322a2fba7e7a Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 29 Sep 2019 04:30:17 +1000 Subject: Poll the database a maximum of 10 attempts before exit code 1. --- manage.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'manage.py') diff --git a/manage.py b/manage.py index baf7333a..44435de5 100755 --- a/manage.py +++ b/manage.py @@ -96,7 +96,9 @@ class SiteManager: # Attempt to connect to the database socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - while True: + + attempts_left = 10 + while attempts_left: try: # Ignore 'incomplete startup packet' s.connect((domain, port)) @@ -104,8 +106,12 @@ class SiteManager: print("Database is ready.") break except socket.error: + attempts_left -= 1 print("Not ready yet, retrying.") time.sleep(0.5) + else: + print("Database could not be found, exiting.") + sys.exit(1) def prepare_server(self) -> None: """Perform preparation tasks before running the server.""" -- cgit v1.2.3