Skip to content

Commit

Permalink
Merge pull request #312 from maykinmedia/feature/conf-env
Browse files Browse the repository at this point in the history
Environment config for Sentry
  • Loading branch information
joeribekker authored Nov 22, 2022
2 parents 31c7921 + c8ccd3d commit 45ea40c
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 137 deletions.
7 changes: 5 additions & 2 deletions docs/installation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,12 @@ can be configured by setting the following environment variables

Other settings
--------------
* ``ENVIRONMENT``: An identifier for the environment, displayed in the admin depending on
the settings module used and included in the error monitoring (see ``SENTRY_DSN``).
The default is set according to ``DJANGO_SETTINGS_MODULE``. Good examples values are:

* ``ADMINS``: Comma seperated list (without spaces!) of e-mail addresses to
sent an email in the case of any errors. Defaults to an empty list.
* ``production``
* ``test``

* ``SITE_ID``: The database ID of the site object. Defaults to ``1``.

Expand Down
67 changes: 32 additions & 35 deletions src/objects/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
from sentry_sdk.integrations import django, redis

from .api import * # noqa
from .utils import config

try:
from sentry_sdk.integrations import celery
except Exception: # no celery in this proejct
except Exception: # no celery in this project
celery = None

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
Expand All @@ -20,27 +21,28 @@
os.path.join(DJANGO_PROJECT_DIR, os.path.pardir, os.path.pardir)
)

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
#
# Core Django settings
#
SITE_ID = config("SITE_ID", 1)

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv("SECRET_KEY")
SECRET_KEY = config("SECRET_KEY")

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

IS_HTTPS = os.getenv("IS_HTTPS", not DEBUG)
DEBUG = config("DEBUG", default=False)

ALLOWED_HOSTS = []
IS_HTTPS = config("IS_HTTPS", not DEBUG)
ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="", split=True)

DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.postgis",
"NAME": os.getenv("DB_NAME", "objects"),
"USER": os.getenv("DB_USER", "objects"),
"PASSWORD": os.getenv("DB_PASSWORD", "objects"),
"HOST": os.getenv("DB_HOST", "localhost"),
"PORT": os.getenv("DB_PORT", 5432),
"NAME": config("DB_NAME", "objects"),
"USER": config("DB_USER", "objects"),
"PASSWORD": config("DB_PASSWORD", "objects"),
"HOST": config("DB_HOST", "localhost"),
"PORT": config("DB_PORT", 5432),
}
}

Expand Down Expand Up @@ -307,12 +309,19 @@
LOGIN_REDIRECT_URL = reverse_lazy("admin:index")
LOGOUT_REDIRECT_URL = reverse_lazy("admin:index")

#
# SECURITY settings
#
SESSION_COOKIE_SECURE = IS_HTTPS
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = IS_HTTPS

#
# Custom settings
#
PROJECT_NAME = "Objects"
SITE_TITLE = "Starting point"
ENVIRONMENT = None
ENVIRONMENT = config("ENVIRONMENT", "")
SHOW_ALERT = True

#
Expand Down Expand Up @@ -369,7 +378,7 @@
HIJACK_ALLOW_GET_REQUESTS = True

# Sentry SDK
SENTRY_DSN = os.getenv("SENTRY_DSN")
SENTRY_DSN = config("SENTRY_DSN", None)

SENTRY_SDK_INTEGRATIONS = [
django.DjangoIntegration(),
Expand All @@ -383,7 +392,8 @@

SENTRY_CONFIG = {
"dsn": SENTRY_DSN,
"release": os.getenv("VERSION_TAG", "VERSION_TAG not set"),
"release": config("VERSION_TAG", "VERSION_TAG not set"),
"environment": ENVIRONMENT,
}

sentry_sdk.init(
Expand All @@ -393,10 +403,10 @@
#
# Elastic APM
#
ELASTIC_APM_SERVER_URL = os.getenv("ELASTIC_APM_SERVER_URL", None)
ELASTIC_APM_SERVER_URL = config("ELASTIC_APM_SERVER_URL", None)
ELASTIC_APM = {
"SERVICE_NAME": os.getenv("ELASTIC_APM_SERVICE_NAME", "Objects API"),
"SECRET_TOKEN": os.getenv("ELASTIC_APM_SECRET_TOKEN", "default"),
"SERVICE_NAME": config("ELASTIC_APM_SERVICE_NAME", "Objects API"),
"SECRET_TOKEN": config("ELASTIC_APM_SECRET_TOKEN", "default"),
"SERVER_URL": ELASTIC_APM_SERVER_URL,
"ENABLED": bool(ELASTIC_APM_SERVER_URL),
}
Expand All @@ -406,32 +416,19 @@
"elasticapm.contrib.django",
]

SITE_ID = os.getenv("SITE_ID", 1)

# VNG API Common
CUSTOM_CLIENT_FETCHER = "objects.utils.client.get_client"

# settings for sending notifications
NOTIFICATIONS_KANAAL = "objecten"
NOTIFICATIONS_DISABLED = os.getenv("NOTIFICATIONS_DISABLED", False) in [
"True",
"true",
"yes",
]
NOTIFICATIONS_DISABLED = config("NOTIFICATIONS_DISABLED", False)

#
# Maykin fork of DJANGO-TWO-FACTOR-AUTH
#
TWO_FACTOR_FORCE_OTP_ADMIN = os.getenv("TWO_FACTOR_FORCE_OTP_ADMIN", "True") in [
"True",
"true",
"yes",
]
TWO_FACTOR_PATCH_ADMIN = os.getenv("TWO_FACTOR_PATCH_ADMIN", "True") in [
"True",
"true",
"yes",
]
TWO_FACTOR_FORCE_OTP_ADMIN = config("TWO_FACTOR_FORCE_OTP_ADMIN", not DEBUG)
TWO_FACTOR_PATCH_ADMIN = config("TWO_FACTOR_PATCH_ADMIN", True)

#
# Mozilla Django OIDC DB settings
Expand Down
4 changes: 2 additions & 2 deletions src/objects/conf/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import os

os.environ.setdefault("SECRET_KEY", "dummy")
os.environ.setdefault("IS_HTTPS", "no")
os.environ.setdefault("ENVIRONMENT", "ci")

from .base import * # noqa isort:skip

Expand All @@ -20,15 +22,13 @@
LOGGING = None # Quiet is nice
logging.disable(logging.CRITICAL)

ENVIRONMENT = "ci"

#
# Django-axes
#
AXES_BEHIND_REVERSE_PROXY = False

NOTIFICATIONS_DISABLED = True
IS_HTTPS = False


#
Expand Down
31 changes: 4 additions & 27 deletions src/objects/conf/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,26 @@
import sys
import warnings

os.environ.setdefault("DEBUG", "yes")
os.environ.setdefault("ALLOWED_HOSTS", "*")
os.environ.setdefault(
"SECRET_KEY", "2(@f(-6s_u(5fd&1sg^uvu2s(c-9sapw)1era8q&)g)h@cwxxg"
)
os.environ.setdefault("IS_HTTPS", "no")
os.environ.setdefault("ENVIRONMENT", "development")

# uses postgresql by default, see base.py
os.environ.setdefault("DB_NAME", "objects"),
os.environ.setdefault("DB_USER", "objects"),
os.environ.setdefault("DB_PASSWORD", "objects"),

from .base import * # noqa isort:skip

# Feel free to switch dev to sqlite3 for simple projects,
# or override DATABASES in your local.py
# DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'

#
# Standard Django settings.
#

DEBUG = True
IS_HTTPS = False
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

ADMINS = ()
MANAGERS = ADMINS

# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]

LOGGING["loggers"].update(
{
"objects": {
Expand Down Expand Up @@ -66,19 +56,6 @@
}
)

#
# Additional Django settings
#

# Disable security measures for development
SESSION_COOKIE_SECURE = False
SESSION_COOKIE_HTTPONLY = False
CSRF_COOKIE_SECURE = False

#
# Custom settings
#
ENVIRONMENT = "development"

#
# Library settings
Expand Down
77 changes: 6 additions & 71 deletions src/objects/conf/docker.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,17 @@
import os

from django.core.exceptions import ImproperlyConfigured

os.environ.setdefault("DB_USER", os.getenv("DB_USER", "objects"))
os.environ.setdefault("DB_NAME", os.getenv("DB_NAME", "objects"))
os.environ.setdefault("DB_PASSWORD", os.getenv("DB_PASSWORD", "objects"))
os.environ.setdefault("DB_HOST", os.getenv("DB_HOST", "db"))
os.environ.setdefault("ENVIRONMENT", "docker")

from .base import * # noqa isort:skip

# Helper function
missing_environment_vars = []


def getenv(key, default=None, required=False, split=False):
val = os.getenv(key, default)
if required and val is None:
missing_environment_vars.append(key)
if split and val:
val = val.split(",")
return val

from .utils import config # noqa isort:skip

#
# Standard Django settings.
#
DEBUG = getenv("DEBUG", False)

ADMINS = getenv("ADMINS", split=True)
MANAGERS = ADMINS

# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
ALLOWED_HOSTS = getenv("ALLOWED_HOSTS", "*", split=True)

CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
Expand All @@ -46,7 +24,7 @@ def getenv(key, default=None, required=False, split=False):
}

# Deal with being hosted on a subpath
subpath = getenv("SUBPATH")
subpath = config("SUBPATH", None)
if subpath:
if not subpath.startswith("/"):
subpath = f"/{subpath}"
Expand All @@ -55,52 +33,6 @@ def getenv(key, default=None, required=False, split=False):
STATIC_URL = f"{FORCE_SCRIPT_NAME}{STATIC_URL}"
MEDIA_URL = f"{FORCE_SCRIPT_NAME}{MEDIA_URL}"

# See: docker-compose.yml
# Optional Docker container usage below:
#
# # Elasticsearch
# HAYSTACK_CONNECTIONS = {
# 'default': {
# 'ENGINE': 'haystack.backends.elasticsearch2_backend.Elasticsearch2SearchEngine',
# 'URL': getenv('ELASTICSEARCH_URL', 'http://elasticsearch:9200/'),
# 'INDEX_NAME': 'objects',
# },
# }
#
# # Caching
# CACHES = {
# 'default': {
# 'BACKEND': 'django_redis.cache.RedisCache',
# 'LOCATION': getenv('CACHE_LOCATION', 'redis://redis:6379/1'),
# 'OPTIONS': {
# 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
# 'IGNORE_EXCEPTIONS': True,
# }
# }
# }

#
# Additional Django settings
#

# Disable security measures for development
SESSION_COOKIE_SECURE = getenv("SESSION_COOKIE_SECURE", False)
SESSION_COOKIE_HTTPONLY = getenv("SESSION_COOKIE_HTTPONLY", False)
CSRF_COOKIE_SECURE = getenv("CSRF_COOKIE_SECURE", False)

#
# Custom settings
#
ENVIRONMENT = "docker"

ELASTIC_APM["SERVICE_NAME"] += " " + ENVIRONMENT

if missing_environment_vars:
raise ImproperlyConfigured(
"These environment variables are required but missing: {}".format(
", ".join(missing_environment_vars)
)
)

#
# Library settings
Expand All @@ -109,3 +41,6 @@ def getenv(key, default=None, required=False, split=False):
# django-axes
AXES_BEHIND_REVERSE_PROXY = False
AXES_CACHE = "axes_cache"

# Elastic APM
ELASTIC_APM["SERVICE_NAME"] += " " + ENVIRONMENT
Empty file.
15 changes: 15 additions & 0 deletions src/objects/conf/tests/test_config_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.test import SimpleTestCase

from ..utils import config


class ConfigHelperTests(SimpleTestCase):
def test_empty_list_as_default(self):
value = config("SOME_TEST_ENVVAR", split=True, default=[])

self.assertEqual(value, [])

def test_non_empty_list_as_default(self):
value = config("SOME_TEST_ENVVAR", split=True, default=["foo"])

self.assertEqual(value, ["foo"])
26 changes: 26 additions & 0 deletions src/objects/conf/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import logging

from decouple import Csv, config as _config, undefined

logger = logging.getLogger(__name__)


def config(option: str, default=undefined, *args, **kwargs):
"""
Pull a config parameter from the environment.
Read the config variable ``option``. If it's optional, use the ``default`` value.
Input is automatically cast to the correct type, where the type is derived from the
default value if possible.
Pass ``split=True`` to split the comma-separated input into a list.
"""
if "split" in kwargs:
kwargs.pop("split")
kwargs["cast"] = Csv()
if isinstance(default, list):
default = ",".join(default)

if default is not undefined and default is not None:
kwargs.setdefault("cast", type(default))
return _config(option, default=default, *args, **kwargs)

0 comments on commit 45ea40c

Please sign in to comment.