-
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.
✨ [#1] first draft of setup_notification command
- Loading branch information
1 parent
662a8b4
commit de81826
Showing
7 changed files
with
191 additions
and
3 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 |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import logging | ||
from abc import ABC, abstractmethod | ||
|
||
from django.conf import settings | ||
|
||
from .exceptions import PrerequisiteFailed | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class BaseConfigurationStep(ABC): | ||
verbose_name: str | ||
required_settings: list[str] = [] | ||
enable_setting: str = "" | ||
|
||
def __str__(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 not getattr(settings, var, 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): | ||
""" | ||
Raise an error then configuration step can't be started | ||
""" | ||
|
||
|
||
class ConfigurationRunFailed(ConfigurationException): | ||
""" | ||
Raise an error then configuration process was faulty | ||
""" | ||
|
||
|
||
class SelfTestFailed(ConfigurationException): | ||
""" | ||
Raise an error for failed configuration self-tests. | ||
""" |
Empty file.
Empty file.
96 changes: 96 additions & 0 deletions
96
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,96 @@ | ||
from collections import OrderedDict | ||
|
||
from django.conf import settings | ||
from django.core.management import BaseCommand, CommandError | ||
from django.utils.module_loading import import_string | ||
|
||
from ...configuration import BaseConfigurationStep | ||
from ...exceptions import ConfigurationRunFailed, PrerequisiteFailed, SelfTestFailed | ||
|
||
|
||
class ErrorDict(OrderedDict): | ||
""" | ||
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 Open Zaak configuration. " | ||
"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." | ||
), | ||
) | ||
|
||
def handle(self, **options): | ||
overwrite: bool = options["overwrite"] | ||
|
||
# todo transaction atomic | ||
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()] | ||
|
||
self.stdout.write( | ||
f"Configuration would 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 | ||
for step in configured_steps: | ||
# todo global env to turn off self tests? | ||
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