diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f02ddb4..6391d7e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,7 @@ Unreleased ~~~~~~~~~~ * Add script to get github action errors * Add script to republish failed events +* Add ``edx_arch_experiments.config_watcher`` Django app for monitoring Waffle changes [2.1.0] - 2023-10-10 ~~~~~~~~~~~~~~~~~~~~ diff --git a/edx_arch_experiments/config_watcher/__init__.py b/edx_arch_experiments/config_watcher/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/edx_arch_experiments/config_watcher/apps.py b/edx_arch_experiments/config_watcher/apps.py new file mode 100644 index 0000000..36ea4ec --- /dev/null +++ b/edx_arch_experiments/config_watcher/apps.py @@ -0,0 +1,16 @@ +""" +App for reporting configuration changes to Slack for operational awareness. +""" + +from django.apps import AppConfig + +class ConfigWatcherApp(AppConfig): + """ + Django application to report configuration changes to operators. + """ + name = 'edx_arch_experiments.config_watcher' + + def ready(self): + from .signals import receivers # pylint: disable=import-outside-toplevel + + receivers.connect_receivers() diff --git a/edx_arch_experiments/config_watcher/signals/__init__.py b/edx_arch_experiments/config_watcher/signals/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/edx_arch_experiments/config_watcher/signals/receivers.py b/edx_arch_experiments/config_watcher/signals/receivers.py new file mode 100644 index 0000000..ddc4763 --- /dev/null +++ b/edx_arch_experiments/config_watcher/signals/receivers.py @@ -0,0 +1,65 @@ +""" +Signal receivers for the config watcher. + +Call ``connect_receivers`` to initialize. +""" + +import waffle.models +from django.db.models import signals +from django.dispatch import receiver + + +def _report_waffle_change(model_short_name, instance, created, fields): + verb = "created" if created else "updated" + state_desc = ", ".join(f"{field}={repr(getattr(instance, field))}" for field in fields) + print(f"⚡⚡⚡ Waffle {model_short_name} {instance.name!r} was {verb}. New config: {state_desc}") + + +def _report_waffle_delete(model_short_name, instance): + print(f"💥💥💥 Waffle {model_short_name} {instance.name!r} was deleted") + + +_WAFFLE_MODELS_TO_OBSERVE = [ + { + 'model': waffle.models.Flag, + 'short_name': 'flag', + 'fields': ['everyone', 'percent', 'superusers', 'staff', 'authenticated', 'note', 'languages'], + }, + { + 'model': waffle.models.Switch, + 'short_name': 'switch', + 'fields': ['active', 'note'], + }, + { + 'model': waffle.models.Sample, + 'short_name': 'sample', + 'fields': ['percent', 'note'], + }, +] + + +def _register_waffle_observation(*, model, short_name, fields): + """ + Register a Waffle model for observation. + + Args: + model (class): The model class to monitor + short_name (str): A short descriptive name for an instance of the model, e.g. "flag" + fields (list): Names of fields to report on in the Slack message + """ + @receiver(signals.post_save, sender=model) + def log_waffle_change(*args, instance, created, **kwargs): + _report_waffle_change(short_name, instance, created, fields) + + @receiver(signals.post_delete, sender=model) + def log_waffle_change(*args, instance, **kwargs): + _report_waffle_delete(short_name, instance) + + +def connect_receivers(): + """ + Initialize application's receivers. + """ + for config in _WAFFLE_MODELS_TO_OBSERVE: + # Pass config to function to capture value properly. + _register_waffle_observation(**config) diff --git a/requirements/base.in b/requirements/base.in index ffe4f6c..0935bef 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -3,3 +3,4 @@ Django # Web application framework edx_django_utils +django-waffle # Configuration switches and flags -- used by config_watcher app diff --git a/requirements/base.txt b/requirements/base.txt index 89e1d84..f9ae526 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -20,14 +20,16 @@ django==3.2.22 django-crum==0.7.9 # via edx-django-utils django-waffle==4.0.0 - # via edx-django-utils + # via + # -r requirements/base.in + # edx-django-utils edx-django-utils==5.7.0 # via -r requirements/base.in -newrelic==9.1.0 +newrelic==9.1.1 # via edx-django-utils pbr==5.11.1 # via stevedore -psutil==5.9.5 +psutil==5.9.6 # via edx-django-utils pycparser==2.21 # via cffi diff --git a/requirements/dev.txt b/requirements/dev.txt index 5599a7f..8f4ab3e 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -8,7 +8,7 @@ asgiref==3.7.2 # via # -r requirements/quality.txt # django -astroid==2.15.8 +astroid==3.0.1 # via # -r requirements/quality.txt # pylint @@ -28,7 +28,7 @@ cffi==1.16.0 # pynacl chardet==5.2.0 # via diff-cover -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via # -r requirements/quality.txt # requests @@ -52,12 +52,13 @@ code-annotations==1.5.0 coverage[toml]==7.3.2 # via # -r requirements/quality.txt + # coverage # pytest-cov cryptography==41.0.4 # via # -r requirements/quality.txt # secretstorage -diff-cover==7.7.0 +diff-cover==8.0.0 # via -r requirements/dev.in dill==0.3.7 # via @@ -143,10 +144,6 @@ keyring==24.2.0 # via # -r requirements/quality.txt # twine -lazy-object-proxy==1.9.0 - # via - # -r requirements/quality.txt - # astroid lxml==4.9.3 # via edx-i18n-tools markdown-it-py==3.0.0 @@ -169,7 +166,7 @@ more-itertools==10.1.0 # via # -r requirements/quality.txt # jaraco-classes -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/quality.txt # edx-django-utils @@ -212,7 +209,7 @@ pluggy==1.3.0 # tox polib==1.2.0 # via edx-i18n-tools -psutil==5.9.5 +psutil==5.9.6 # via # -r requirements/quality.txt # edx-django-utils @@ -220,7 +217,7 @@ py==1.11.0 # via # -r requirements/ci.txt # tox -pycodestyle==2.11.0 +pycodestyle==2.11.1 # via -r requirements/quality.txt pycparser==2.21 # via @@ -234,7 +231,7 @@ pygments==2.16.1 # diff-cover # readme-renderer # rich -pylint==2.17.7 +pylint==3.0.2 # via # -r requirements/quality.txt # edx-lint @@ -245,7 +242,7 @@ pylint-celery==0.3 # via # -r requirements/quality.txt # edx-lint -pylint-django==2.5.3 +pylint-django==2.5.5 # via # -r requirements/quality.txt # edx-lint @@ -364,7 +361,7 @@ typing-extensions==4.8.0 # astroid # pylint # rich -urllib3==2.0.6 +urllib3==2.0.7 # via # -r requirements/quality.txt # requests @@ -377,10 +374,6 @@ wheel==0.41.2 # via # -r requirements/pip-tools.txt # pip-tools -wrapt==1.15.0 - # via - # -r requirements/quality.txt - # astroid zipp==3.17.0 # via # -r requirements/pip-tools.txt diff --git a/requirements/doc.txt b/requirements/doc.txt index 96a2d57..d7ab8f3 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -18,7 +18,7 @@ cffi==1.16.0 # via # -r requirements/test.txt # pynacl -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via requests click==8.1.7 # via @@ -30,6 +30,7 @@ code-annotations==1.5.0 coverage[toml]==7.3.2 # via # -r requirements/test.txt + # coverage # pytest-cov django==3.2.22 # via @@ -81,7 +82,7 @@ markupsafe==2.1.3 # via # -r requirements/test.txt # jinja2 -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/test.txt # edx-django-utils @@ -100,7 +101,7 @@ pluggy==1.3.0 # via # -r requirements/test.txt # pytest -psutil==5.9.5 +psutil==5.9.6 # via # -r requirements/test.txt # edx-django-utils @@ -189,7 +190,7 @@ typing-extensions==4.8.0 # via # -r requirements/test.txt # asgiref -urllib3==2.0.6 +urllib3==2.0.7 # via requests zipp==3.17.0 # via importlib-metadata diff --git a/requirements/pip.txt b/requirements/pip.txt index 3e7d8f4..0c788d6 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -8,7 +8,7 @@ wheel==0.41.2 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -pip==23.2.1 +pip==23.3.1 # via -r requirements/pip.in setuptools==68.2.2 # via -r requirements/pip.in diff --git a/requirements/quality.txt b/requirements/quality.txt index bf405c2..1cae0ce 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -8,7 +8,7 @@ asgiref==3.7.2 # via # -r requirements/test.txt # django -astroid==2.15.8 +astroid==3.0.1 # via # pylint # pylint-celery @@ -19,7 +19,7 @@ cffi==1.16.0 # -r requirements/test.txt # cryptography # pynacl -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via requests click==8.1.7 # via @@ -37,6 +37,7 @@ code-annotations==1.5.0 coverage[toml]==7.3.2 # via # -r requirements/test.txt + # coverage # pytest-cov cryptography==41.0.4 # via secretstorage @@ -95,8 +96,6 @@ jinja2==3.1.2 # code-annotations keyring==24.2.0 # via twine -lazy-object-proxy==1.9.0 - # via astroid markdown-it-py==3.0.0 # via rich markupsafe==2.1.3 @@ -109,7 +108,7 @@ mdurl==0.1.2 # via markdown-it-py more-itertools==10.1.0 # via jaraco-classes -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/test.txt # edx-django-utils @@ -131,11 +130,11 @@ pluggy==1.3.0 # via # -r requirements/test.txt # pytest -psutil==5.9.5 +psutil==5.9.6 # via # -r requirements/test.txt # edx-django-utils -pycodestyle==2.11.0 +pycodestyle==2.11.1 # via -r requirements/quality.in pycparser==2.21 # via @@ -147,7 +146,7 @@ pygments==2.16.1 # via # readme-renderer # rich -pylint==2.17.7 +pylint==3.0.2 # via # edx-lint # pylint-celery @@ -155,7 +154,7 @@ pylint==2.17.7 # pylint-plugin-utils pylint-celery==0.3 # via edx-lint -pylint-django==2.5.3 +pylint-django==2.5.5 # via edx-lint pylint-plugin-utils==0.8.2 # via @@ -234,12 +233,10 @@ typing-extensions==4.8.0 # astroid # pylint # rich -urllib3==2.0.6 +urllib3==2.0.7 # via # requests # twine -wrapt==1.15.0 - # via astroid zipp==3.17.0 # via # importlib-metadata diff --git a/requirements/scripts.txt b/requirements/scripts.txt index 21c910a..df24e3e 100644 --- a/requirements/scripts.txt +++ b/requirements/scripts.txt @@ -18,7 +18,7 @@ cffi==1.16.0 # via # -r requirements/base.txt # pynacl -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via requests click==8.1.7 # via @@ -70,7 +70,7 @@ jinja2==3.1.2 # via code-annotations markupsafe==2.1.3 # via jinja2 -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/base.txt # edx-django-utils @@ -80,7 +80,7 @@ pbr==5.11.1 # via # -r requirements/base.txt # stevedore -psutil==5.9.5 +psutil==5.9.6 # via # -r requirements/base.txt # edx-django-utils @@ -121,5 +121,5 @@ typing-extensions==4.8.0 # -r requirements/base.txt # asgiref # edx-opaque-keys -urllib3==2.0.6 +urllib3==2.0.7 # via requests diff --git a/requirements/test.txt b/requirements/test.txt index d0dfd22..50bde78 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -20,7 +20,9 @@ click==8.1.7 code-annotations==1.5.0 # via -r requirements/test.in coverage[toml]==7.3.2 - # via pytest-cov + # via + # coverage + # pytest-cov # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/base.txt @@ -45,7 +47,7 @@ jinja2==3.1.2 # via code-annotations markupsafe==2.1.3 # via jinja2 -newrelic==9.1.0 +newrelic==9.1.1 # via # -r requirements/base.txt # edx-django-utils @@ -57,7 +59,7 @@ pbr==5.11.1 # stevedore pluggy==1.3.0 # via pytest -psutil==5.9.5 +psutil==5.9.6 # via # -r requirements/base.txt # edx-django-utils