-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from maykinmedia/feature/1-config-command
setup_notification command
- Loading branch information
Showing
15 changed files
with
504 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,4 +30,4 @@ jobs: | |
run: pip install tox | ||
- run: tox | ||
env: | ||
TOXENV: $ | ||
TOXENV: ${{ matrix.toxenv }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from abc import ABC, abstractmethod | ||
|
||
from django.conf import settings | ||
|
||
from .exceptions import PrerequisiteFailed | ||
|
||
|
||
class BaseConfigurationStep(ABC): | ||
verbose_name: str | ||
required_settings: list[str] = [] | ||
enable_setting: str = "" | ||
|
||
def __repr__(self): | ||
return self.verbose_name | ||
|
||
def validate_requirements(self) -> None: | ||
""" | ||
check prerequisites of the configuration | ||
:raises: :class: `django_setup_configuration.exceptions.PrerequisiteFailed` | ||
if prerequisites are missing | ||
""" | ||
missing = [ | ||
var | ||
for var in self.required_settings | ||
if getattr(settings, var, None) in [None, ""] | ||
] | ||
if missing: | ||
raise PrerequisiteFailed( | ||
f"{', '.join(missing)} settings should be provided" | ||
) | ||
|
||
def is_enabled(self) -> bool: | ||
""" | ||
Hook to switch on and off the configuration step from env vars | ||
By default all steps are enabled | ||
""" | ||
if not self.enable_setting: | ||
return True | ||
|
||
return getattr(settings, self.enable_setting, True) | ||
|
||
@abstractmethod | ||
def is_configured(self) -> bool: | ||
""" | ||
Check that the configuration is already done with current env variables | ||
""" | ||
... | ||
|
||
@abstractmethod | ||
def configure(self) -> None: | ||
""" | ||
Run the configuration step. | ||
:raises: :class: `django_setup_configuration.exceptions.ConfigurationRunFailed` | ||
if the configuration has an error | ||
""" | ||
... | ||
|
||
@abstractmethod | ||
def test_configuration(self) -> None: | ||
""" | ||
Test that the configuration works as expected | ||
:raises: :class:`openzaak.config.bootstrap.exceptions.SelfTestFailure` | ||
if the configuration aspect was found to be faulty. | ||
""" | ||
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
class ConfigurationException(Exception): | ||
""" | ||
Base exception for configuration steps | ||
""" | ||
|
||
|
||
class PrerequisiteFailed(ConfigurationException): | ||
""" | ||
Raises an error when the configuration step can't be started | ||
""" | ||
|
||
|
||
class ConfigurationRunFailed(ConfigurationException): | ||
""" | ||
Raises an error when the configuration process was faulty | ||
""" | ||
|
||
|
||
class SelfTestFailed(ConfigurationException): | ||
""" | ||
Raises an error for failed configuration self-tests. | ||
""" |
Empty file.
Empty file.
115 changes: 115 additions & 0 deletions
115
django_setup_configuration/management/commands/setup_configuration.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
from django.conf import settings | ||
from django.core.management import BaseCommand, CommandError | ||
from django.db import transaction | ||
from django.utils.module_loading import import_string | ||
|
||
from ...configuration import BaseConfigurationStep | ||
from ...exceptions import ConfigurationRunFailed, PrerequisiteFailed, SelfTestFailed | ||
|
||
|
||
class ErrorDict(dict): | ||
""" | ||
small helper to display errors | ||
""" | ||
|
||
def as_text(self) -> str: | ||
output = [f"{k}: {v}" for k, v in self.items()] | ||
return "\n".join(output) | ||
|
||
|
||
class Command(BaseCommand): | ||
help = ( | ||
"Bootstrap the initial configuration of the application. " | ||
"This command is run only in non-interactive mode with settings " | ||
"configured mainly via environment variables." | ||
) | ||
output_transaction = True | ||
|
||
def add_arguments(self, parser): | ||
parser.add_argument( | ||
"--overwrite", | ||
action="store_true", | ||
help=( | ||
"Overwrite the existing configuration. Should be used if some " | ||
"of the env variables have been changed." | ||
), | ||
) | ||
parser.add_argument( | ||
"--no-selftest", | ||
action="store_true", | ||
dest="skip_selftest", | ||
help=( | ||
"Skip checking if configuration is successful. Use it if you " | ||
"run this command in the init container before the web app is started" | ||
), | ||
) | ||
|
||
@transaction.atomic | ||
def handle(self, **options): | ||
overwrite: bool = options["overwrite"] | ||
skip_selftest: bool = options["skip_selftest"] | ||
|
||
errors = ErrorDict() | ||
steps: list[BaseConfigurationStep] = [ | ||
import_string(path)() for path in settings.SETUP_CONFIGURATION_STEPS | ||
] | ||
enabled_steps = [step for step in steps if step.is_enabled()] | ||
|
||
if not enabled_steps: | ||
self.stdout.write( | ||
"There are no enabled configuration steps. " | ||
"Configuration can't be set up" | ||
) | ||
return | ||
|
||
self.stdout.write( | ||
f"Configuration will be set up with following steps: {enabled_steps}" | ||
) | ||
|
||
# 1. Check prerequisites of all steps | ||
for step in enabled_steps: | ||
try: | ||
step.validate_requirements() | ||
except PrerequisiteFailed as exc: | ||
errors[step] = exc | ||
|
||
if errors: | ||
raise CommandError( | ||
f"Prerequisites for configuration are not fulfilled: {errors.as_text()}" | ||
) | ||
|
||
# 2. Configure steps | ||
configured_steps = [] | ||
for step in enabled_steps: | ||
if not overwrite and step.is_configured(): | ||
self.stdout.write( | ||
f"Step {step} is skipped, because the configuration already exists." | ||
) | ||
continue | ||
else: | ||
self.stdout.write(f"Configuring {step}...") | ||
try: | ||
step.configure() | ||
except ConfigurationRunFailed as exc: | ||
raise CommandError(f"Could not configure step {step}") from exc | ||
else: | ||
self.stdout.write(f"{step} is successfully configured") | ||
configured_steps.append(step) | ||
|
||
# 3. Test configuration | ||
if skip_selftest: | ||
self.stdout.write("Selftest is skipped.") | ||
|
||
else: | ||
for step in configured_steps: | ||
try: | ||
step.test_configuration() | ||
except SelfTestFailed as exc: | ||
errors[step] = exc | ||
|
||
if errors: | ||
raise CommandError( | ||
f"Configuration test failed with errors: {errors.as_text()}" | ||
) | ||
|
||
self.stdout.write(self.style.SUCCESS("Instance configuration completed.")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,16 +5,17 @@ build-backend = "setuptools.build_meta" | |
[project] | ||
name = "django_setup_configuration" | ||
version = "0.1.0" | ||
description = "TODO" | ||
description = "Pluggable configuration setup used with the django management command" | ||
authors = [ | ||
{name = "Maykin Media", email = "[email protected]"} | ||
] | ||
readme = "README.rst" | ||
license = {file = "LICENSE"} | ||
keywords = ["TODO"] | ||
keywords = ["Django", "Configuration"] | ||
classifiers = [ | ||
"Development Status :: 3 - Alpha", | ||
"Framework :: Django", | ||
"Framework :: Django :: 3.2", | ||
"Framework :: Django :: 4.2", | ||
"Intended Audience :: Developers", | ||
"Operating System :: Unix", | ||
|
@@ -27,19 +28,21 @@ classifiers = [ | |
] | ||
requires-python = ">=3.10" | ||
dependencies = [ | ||
"django>=4.2" | ||
"django>=3.2" | ||
] | ||
|
||
[project.urls] | ||
Homepage = "https://github.com/maykinmedia/django_setup_configuration" | ||
Documentation = "http://django_setup_configuration.readthedocs.io/en/latest/" | ||
"Bug Tracker" = "https://github.com/maykinmedia/django_setup_configuration/issues" | ||
"Source Code" = "https://github.com/maykinmedia/django_setup_configuration" | ||
Homepage = "https://github.com/maykinmedia/django-setup-configuration" | ||
Documentation = "http://django-setup-configuration.readthedocs.io/en/latest/" | ||
"Bug Tracker" = "https://github.com/maykinmedia/django-setup-configuration/issues" | ||
"Source Code" = "https://github.com/maykinmedia/django-setup-configuration" | ||
|
||
[project.optional-dependencies] | ||
tests = [ | ||
"pytest", | ||
"pytest-django", | ||
"pytest-mock", | ||
"furl", | ||
"tox", | ||
"isort", | ||
"black", | ||
|
Oops, something went wrong.