From 255e618ad7ed4f15360db11b92479ae53e5bd28f Mon Sep 17 00:00:00 2001 From: Marcin Tomiczek Date: Wed, 20 Dec 2023 14:08:05 +0100 Subject: [PATCH] Update docker-compose and checks to wait for db to start up --- .github/workflows/checks.yml | 2 +- app/app/settings.py | 9 ++++-- app/core/__init__.py | 0 app/core/admin.py | 3 ++ app/core/apps.py | 6 ++++ app/core/management/__init__.py | 0 app/core/management/commands/__init__.py | 0 app/core/management/commands/wait_for_db.py | 24 ++++++++++++++++ app/core/migrations/__init__.py | 0 app/core/models.py | 3 ++ app/core/tests/__init__.py | 0 app/core/tests/test_commands.py | 32 +++++++++++++++++++++ docker-compose.yml | 4 ++- 13 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 app/core/__init__.py create mode 100644 app/core/admin.py create mode 100644 app/core/apps.py create mode 100644 app/core/management/__init__.py create mode 100644 app/core/management/commands/__init__.py create mode 100644 app/core/management/commands/wait_for_db.py create mode 100644 app/core/migrations/__init__.py create mode 100644 app/core/models.py create mode 100644 app/core/tests/__init__.py create mode 100644 app/core/tests/test_commands.py diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 61c7284..b92dc0b 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -16,6 +16,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Test - run: docker-compose run --rm app sh -c "python manage.py test" + run: docker-compose run --rm app sh -c "python manage.py wait_for_db && python manage.py test" - name: Lint run: docker-compose run --rm app sh -c "flake8" \ No newline at end of file diff --git a/app/app/settings.py b/app/app/settings.py index abf9c25..d5484a0 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/ """ +import os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -37,6 +38,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'core', ] MIDDLEWARE = [ @@ -75,8 +77,11 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + 'ENGINE': 'django.db.backends.postgresql', + 'HOST': os.environ.get('DB_HOST'), + 'NAME': os.environ.get('DB_NAME'), + 'USER': os.environ.get('DB_USER'), + 'PASSWORD': os.environ.get('DB_PASS'), } } diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/admin.py b/app/core/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/app/core/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/app/core/apps.py b/app/core/apps.py new file mode 100644 index 0000000..8115ae6 --- /dev/null +++ b/app/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core' diff --git a/app/core/management/__init__.py b/app/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/management/commands/__init__.py b/app/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/management/commands/wait_for_db.py b/app/core/management/commands/wait_for_db.py new file mode 100644 index 0000000..336e552 --- /dev/null +++ b/app/core/management/commands/wait_for_db.py @@ -0,0 +1,24 @@ +"""Django command to wait for the database to be available.""" +import time + +from psycopg2 import OperationalError as Psycopg2Error +from django.db.utils import OperationalError +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + """Django command to wait for database.""" + + def handle(self, *args, **options): + """Entrypoint for command.""" + self.stdout.write("Waiting for database...") + db_up = False + while not db_up: + try: + self.check(databases=['default']) + db_up = True + except (Psycopg2Error, OperationalError): + self.stdout.write("Database unavailable, waiting 1s...") + time.sleep(1) + + self.stdout.write(self.style.SUCCESS("Database available!")) diff --git a/app/core/migrations/__init__.py b/app/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/models.py b/app/core/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/app/core/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/app/core/tests/__init__.py b/app/core/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/tests/test_commands.py b/app/core/tests/test_commands.py new file mode 100644 index 0000000..92c21e9 --- /dev/null +++ b/app/core/tests/test_commands.py @@ -0,0 +1,32 @@ +"""Test custom Django management commands.""" +from unittest.mock import patch + +from psycopg2 import OperationalError as Psycopg2Error +from django.core.management import call_command +from django.db.utils import OperationalError +from django.test import SimpleTestCase + + +@patch("core.management.commands.wait_for_db.Command.check") +class CommandTests(SimpleTestCase): + """Test commands.""" + + def test_wait_for_db_ready(self, check): + """Test waiting for database if database ready.""" + check.return_value = True + + call_command("wait_for_db") + + check.assert_called_once_with(databases=['default']) + + def test_wait_for_db_delay(self, check): + """Test waiting for database when getting OperationalError.""" + check.side_effect = ( + [Psycopg2Error] * 2 + [OperationalError] * 3 + [True] + ) + + with patch("time.sleep"): + call_command("wait_for_db") + + self.assertEquals(check.call_count, 6) + check.assert_called_with(databases=['default']) diff --git a/docker-compose.yml b/docker-compose.yml index 2eac82a..a14bc66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,9 @@ services: volumes: - ./app:/app command: > - sh -c "python manage.py runserver 0.0.0.0:8000" + sh -c "python manage.py wait_for_db && + python manage.py migrate && + python manage.py runserver 0.0.0.0:8000" environment: - DB_HOST=db - DB_NAME=devdb