From 0863284a50ad67677cb660e09b3f5e989b20b727 Mon Sep 17 00:00:00 2001 From: Klaas van Schelven Date: Mon, 13 May 2024 23:07:20 +0200 Subject: [PATCH] WIP (midway checkin): create_example_conf.py --- bugsink/scripts/__init__.py | 0 bugsink/scripts/create_example_conf.py | 83 ++++++++++++++++++++ bugsink/settings/__init__.py | 0 bugsink/{settings.py => settings/default.py} | 81 ++++++------------- bugsink/settings/development.py | 49 ++++++++++++ 5 files changed, 157 insertions(+), 56 deletions(-) create mode 100644 bugsink/scripts/__init__.py create mode 100644 bugsink/scripts/create_example_conf.py create mode 100644 bugsink/settings/__init__.py rename bugsink/{settings.py => settings/default.py} (76%) create mode 100644 bugsink/settings/development.py diff --git a/bugsink/scripts/__init__.py b/bugsink/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bugsink/scripts/create_example_conf.py b/bugsink/scripts/create_example_conf.py new file mode 100644 index 0000000..b30b878 --- /dev/null +++ b/bugsink/scripts/create_example_conf.py @@ -0,0 +1,83 @@ +import argparse +import os +import sys + +from django.core.management.utils import get_random_secret_key + + +def main(): + parser = argparse.ArgumentParser(description="Create a configuration file for the example") + parser.add_argument("--output-file", "-o", help="Output file", default="bugsink_conf.py") + args = parser.parse_args() + + if os.path.exists(args.output_file): + print("Output file already exists; please remove it first") + sys.exit(1) + + secret_key = get_random_secret_key() + + with open(args.output_file, "w") as f: + f.write('''# auto-generated example bugsink_conf.py + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "''' + secret_key + '''" + +# Alternatively, pass the SECRET_KEY as an environment variable. (although that has security implications too!) +# i.e. those may leak in shared server setups. +# +# SECRET_KEY = os.getenv("SECRET_KEY") + + +# See TODO in the docs +SNAPPEA = { + "TASK_ALWAYS_EAGER": True, + "NUM_WORKERS": 1, +} + + +# EMAIL_HOST = ... +# EMAIL_HOST_USER = ... +# EMAIL_HOST_PASSWORD = ... +# EMAIL_PORT = ... +# EMAIL_USE_TLS = ... + +SERVER_EMAIL = DEFAULT_FROM_EMAIL = "Bugsink " + +BUGSINK = { + # See TODO in the docs + # "DIGEST_IMMEDIATELY": False, + + # "MAX_EVENT_SIZE": _MEBIBYTE, + # "MAX_EVENT_COMPRESSED_SIZE": 200 * _KIBIBYTE, + # "MAX_ENVELOPE_SIZE": 100 * _MEBIBYTE, + # "MAX_ENVELOPE_COMPRESSED_SIZE": 20 * _MEBIBYTE, + + # "BASE_URL": "http://bugsink:9000", # no trailing slash + # "SITE_TITLE": "Bugsink", # you can customize this as e.g. "My Bugsink" or "Bugsink for My Company" +} + +''') + + +# some thoughts that I haven't been able to squish into a short comment yet: + +# 1. regarding env-variables v.s. just using a conf file: we're not religious about Twelve Factor at all, but even if we +# were 12-factor says the following (which is basically what we do): + +# > Another approach to config is the use of config files which are not checked into revision control, such as +# > config/database.yml in Rails. This is a huge improvement over using constants which are checked into the code repo, +# > but still has weaknesses: it’s easy to mistakenly check in a config file to the repo; there is a tendency for config +# > files to be scattered about in different places and different formats, making it hard to see and manage all the +# > config in one place. Further, these formats tend to be language- or framework-specific. + +# regarding the mentioned drawbacks: check-in is unlikely (this conf is owned by end-users, not bugsink-programmers) and +# scattering is not what we do: we have just a single file for the bugsink conf (though there are separate ones for e.g. +# gunicorn and nginx, but I'd argue that's a good thing) +# +# 2. when comparing with the auto-generated django settings, the SECRET_KEY that we generate here is less likely to be +# "automatically exposed", because it is not automatically part of the source code (checked into version control) of +# some piece of software. Still, people could check this file in, and you'd have an exposed key in that case. +# +# 3. regarding the sensitivity of this key, and storing it in the file system: I'd argue that if the server you're +# running bugsink on is compromised (and the file can be read) you have bigger problems (since the DB is also on that +# server) diff --git a/bugsink/settings/__init__.py b/bugsink/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bugsink/settings.py b/bugsink/settings/default.py similarity index 76% rename from bugsink/settings.py rename to bugsink/settings/default.py index 67a7723..2d7d240 100644 --- a/bugsink/settings.py +++ b/bugsink/settings/default.py @@ -4,13 +4,24 @@ import sys from pathlib import Path -import sentry_sdk -from sentry_sdk.integrations.django import DjangoIntegration - from django.utils.log import DEFAULT_LOGGING from debug_toolbar.middleware import show_toolbar + +# We have a single file for our default settings, and expect (if they use the recommended setup) the end-users to +# configure their setup using a single bugsink_conf.py also. To be able to have (slightly) different settings for e.g. +# logging for various commands, we expose a variable I_AM_RUNNING that can be used to determine what command is being +# run. We use (potentially fragile) sys.argv checks to determine what command is being run. For now "it works, don't +# fix it" +if sys.argv[1:2] == ['runsnappea']: + I_AM_RUNNING = "SNAPPEA" +elif sys.argv[1:2] == ['test']: + I_AM_RUNNING = "TEST" +else: + I_AM_RUNNING = "OTHER" + + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -18,8 +29,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'django-insecure-$@clhhieazwnxnha-_zah&(bieq%yux7#^07&xsvhn58t)8@xw' -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = False ALLOWED_HOSTS = ["*"] # SECURITY WARNING: also make production-worthy @@ -193,10 +203,11 @@ DEBUG_TOOLBAR_CONFIG = { LOGGING = deepcopy(DEFAULT_LOGGING) -# Django's standard logging has LOGGING['handlers']['console']['filters'] = ['require_debug_true']; our app is -# configured (by default at least) to just spit everything on stdout, even in production. stdout is picked up by -# gunicorn, and we can "take it from there". -LOGGING['handlers']['console']['filters'] = [] +if I_AM_RUNNING != "TEST": + # Django's standard logging has LOGGING['handlers']['console']['filters'] = ['require_debug_true']; our app is + # configured (by default at least) to just spit everything on stdout, even in production. stdout is picked up by + # gunicorn, and we can "take it from there". + LOGGING['handlers']['console']['filters'] = [] LOGGING['loggers']['bugsink'] = { "level": "INFO", @@ -210,7 +221,7 @@ LOGGING["formatters"]["snappea"] = { } LOGGING["handlers"]["snappea"] = { - "level": "DEBUG" if DEBUG else "INFO", + "level": "DEBUG" if DEBUG else "INFO", # TODO this won't work either. but this I can do more classically (development.py) "class": "logging.StreamHandler" } @@ -221,51 +232,9 @@ LOGGING['loggers']['snappea'] = { "handlers": ["snappea"], } -# TODO sys.argv checking: how do I want to deal with it in my final config setup? -if sys.argv[1:2] == ['runsnappea']: + +if I_AM_RUNNING == "SNAPPEA": + # We set all handlers to the snappea handler in this case: this way the things that are logged inside individual + # workers show up with the relevant worker-annotations (i.e. threadName). for logger in LOGGING['loggers'].values(): logger["handlers"] = ["snappea"] - - -# ###################### MOST PER-SITE CONFIG BELOW THIS LINE ################### - - -# {PROTOCOL}://{PUBLIC_KEY}:{DEPRECATED_SECRET_KEY}@{HOST}{PATH}/{PROJECT_ID} -SENTRY_DSN = os.getenv("SENTRY_DSN") - - -if SENTRY_DSN is not None: - sentry_sdk.init( - dsn=SENTRY_DSN, - integrations=[DjangoIntegration()], - auto_session_tracking=False, - traces_sample_rate=0, - send_default_pii=True, - ) - -SNAPPEA = { - "TASK_ALWAYS_EAGER": True, - "NUM_WORKERS": 1, -} - -POSTMARK_API_KEY = os.getenv('POSTMARK_API_KEY') - -EMAIL_HOST = 'smtp.postmarkapp.com' -EMAIL_HOST_USER = POSTMARK_API_KEY -EMAIL_HOST_PASSWORD = POSTMARK_API_KEY -EMAIL_PORT = 587 -EMAIL_USE_TLS = True - -SERVER_EMAIL = DEFAULT_FROM_EMAIL = 'Klaas van Schelven ' - -BUGSINK = { - "DIGEST_IMMEDIATELY": False, - - # "MAX_EVENT_SIZE": _MEBIBYTE, - # "MAX_EVENT_COMPRESSED_SIZE": 200 * _KIBIBYTE, - # "MAX_ENVELOPE_SIZE": 100 * _MEBIBYTE, - # "MAX_ENVELOPE_COMPRESSED_SIZE": 20 * _MEBIBYTE, - - "BASE_URL": "http://bugsink:9000", # no trailing slash - "SITE_TITLE": "Bugsink", # you can customize this as e.g. "My Bugsink" or "Bugsink for My Company" -} diff --git a/bugsink/settings/development.py b/bugsink/settings/development.py new file mode 100644 index 0000000..ec86df7 --- /dev/null +++ b/bugsink/settings/development.py @@ -0,0 +1,49 @@ +from .default import * # noqa + +import os + +import sentry_sdk +from sentry_sdk.integrations.django import DjangoIntegration + + +DEBUG = True + +# {PROTOCOL}://{PUBLIC_KEY}:{DEPRECATED_SECRET_KEY}@{HOST}{PATH}/{PROJECT_ID} +SENTRY_DSN = os.getenv("SENTRY_DSN") + + +if SENTRY_DSN is not None: + sentry_sdk.init( + dsn=SENTRY_DSN, + integrations=[DjangoIntegration()], + auto_session_tracking=False, + traces_sample_rate=0, + send_default_pii=True, + ) + +SNAPPEA = { + "TASK_ALWAYS_EAGER": True, + "NUM_WORKERS": 1, +} + +POSTMARK_API_KEY = os.getenv('POSTMARK_API_KEY') + +EMAIL_HOST = 'smtp.postmarkapp.com' +EMAIL_HOST_USER = POSTMARK_API_KEY +EMAIL_HOST_PASSWORD = POSTMARK_API_KEY +EMAIL_PORT = 587 +EMAIL_USE_TLS = True + +SERVER_EMAIL = DEFAULT_FROM_EMAIL = 'Klaas van Schelven ' + +BUGSINK = { + "DIGEST_IMMEDIATELY": False, + + # "MAX_EVENT_SIZE": _MEBIBYTE, + # "MAX_EVENT_COMPRESSED_SIZE": 200 * _KIBIBYTE, + # "MAX_ENVELOPE_SIZE": 100 * _MEBIBYTE, + # "MAX_ENVELOPE_COMPRESSED_SIZE": 20 * _MEBIBYTE, + + "BASE_URL": "http://bugsink:9000", # no trailing slash + "SITE_TITLE": "Bugsink", # you can customize this as e.g. "My Bugsink" or "Bugsink for My Company" +}