From c4543bce58a27c7ef4d7cb744e2fa0dd5002f9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Mon, 22 Oct 2018 17:25:58 +0300 Subject: [PATCH] Load settings from environment variables. Fixes #187 --- CHANGELOG.md | 6 +++ README.md | 111 +++++++++++++++++++++++++-------------------- hc/settings.py | 121 ++++++++++++++++++++++++++++--------------------- 3 files changed, 139 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbc5a162..c49a6a91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog All notable changes to this project will be documented in this file. +## Unreleased + +### Improvements +- Load settings from environment variables + + ## 1.2.0 - 2018-10-20 ### Improvements diff --git a/README.md b/README.md index ae014555..a989898c 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,54 @@ visit `http://localhost:8080/admin` ## Configuration -Site configuration is kept in `hc/settings.py`. Additional configuration -is loaded from `hc/local_settings.py` file, if it exists. You -can create this file (should be right next to `settings.py` in the filesystem) -and override settings as needed. +Site configuration is loaded from environment variables. This is +done in `hc/settings.py`. Additional configuration is loaded +from `hc/local_settings.py` file, if it exists. You can create this file +(should be right next to `settings.py` in the filesystem) and override +settings, or add extra settings as needed. + +Configurations settings loaded from environment variables: + +| Environment variable | Default value | Notes +| -------------------- | ------------- | ----- | +| [SECRET_KEY](https://docs.djangoproject.com/en/2.1/ref/settings/#secret-key) | `"---"` +| [DEBUG](https://docs.djangoproject.com/en/2.1/ref/settings/#debug) | `True` | Set to `False` for production +| [ALLOWED_HOSTS](https://docs.djangoproject.com/en/2.1/ref/settings/#allowed-hosts) | `*` | Separate multiple hosts with commas +| [DEFAULT_FROM_EMAIL](https://docs.djangoproject.com/en/2.1/ref/settings/#default-from-email) | `"healthchecks@example.org"` +| USE_PAYMENTS | `False` +| REGISTRATION_OPEN | `True` +| DB | `"sqlite"` | Set to `"postgres"` or `"mysql"` +| [DB_HOST](https://docs.djangoproject.com/en/2.1/ref/settings/#host) | `""` *(empty string)* +| [DB_PORT](https://docs.djangoproject.com/en/2.1/ref/settings/#port) | `""` *(empty string)* +| [DB_NAME](https://docs.djangoproject.com/en/2.1/ref/settings/#name) | `"hc"` +| [DB_USER](https://docs.djangoproject.com/en/2.1/ref/settings/#user) | `"postgres"` or `"root"` +| [DB_PASSWORD](https://docs.djangoproject.com/en/2.1/ref/settings/#password) | `""` *(empty string)* +| [DB_CONN_MAX_AGE](https://docs.djangoproject.com/en/2.1/ref/settings/#conn-max-age) | `0` +| DB_SSLMODE | `"prefer"` | PostgreSQL-specific, [details](https://blog.github.com/2018-10-21-october21-incident-report/) +| DB_TARGET_SESSION_ATTRS | `"read-write"` | PostgreSQL-specific, [details](https://www.postgresql.org/docs/10/static/libpq-connect.html#LIBPQ-CONNECT-TARGET-SESSION-ATTRS) +| SITE_ROOT | `"http://localhost:8000"` +| SITE_NAME | `"Mychecks"` +| MASTER_BADGE_LABEL | `"Mychecks"` +| PING_ENDPOINT | `"http://localhost:8000/ping/"` +| PING_EMAIL_DOMAIN | `"localhost"` +| DISCORD_CLIENT_ID | `None` +| DISCORD_CLIENT_SECRET | `None` +| SLACK_CLIENT_ID | `None` +| SLACK_CLIENT_SECRET | `None` +| PUSHOVER_API_TOKEN | `None` +| PUSHOVER_SUBSCRIPTION_URL | `None` +| PUSHOVER_EMERGENCY_RETRY_DELAY | `300` +| PUSHOVER_EMERGENCY_EXPIRATION | `86400` +| PUSHBULLET_CLIENT_ID | `None` +| PUSHBULLET_CLIENT_SECRET | `None` +| TELEGRAM_BOT_NAME | `"ExampleBot"` +| TELEGRAM_TOKEN | `None` +| TWILIO_ACCOUNT | `None` +| TWILIO_AUTH | `None` +| TWILIO_FROM | `None` +| PD_VENDOR_KEY | `None` +| TRELLO_APP_KEY | `None` + Some useful settings keys to override are: @@ -109,10 +153,9 @@ to your team account. ## Database Configuration -Database configuration is stored in `hc/settings.py` and can be overriden -in `hc/local_settings.py`. The default database engine is SQLite. To use -PostgreSQL, create `hc/local_settings.py` if it does not exist, and put the -following in it, changing it as neccessary: +Database configuration is loaded from environment variables. If you +need to use a non-standard configuration, you can override the +database configuration in `hc/local_settings.py` like so: ```python DATABASES = { @@ -121,38 +164,10 @@ DATABASES = { 'NAME': 'your-database-name-here', 'USER': 'your-database-user-here', 'PASSWORD': 'your-database-password-here', - 'TEST': {'CHARSET': 'UTF8'} - } -} -``` - -For MySQL: - -```python -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'your-database-name-here', - 'USER': 'your-database-user-here', - 'PASSWORD': 'your-database-password-here', - 'TEST': {'CHARSET': 'UTF8'} - } -} -``` - -You can also use `hc/local_settings.py` to read database -configuration from environment variables like so: - -```python -import os - -DATABASES = { - 'default': { - 'ENGINE': os.environ['DB_ENGINE'], - 'NAME': os.environ['DB_NAME'], - 'USER': os.environ['DB_USER'], - 'PASSWORD': os.environ['DB_PASSWORD'], - 'TEST': {'CHARSET': 'UTF8'} + 'TEST': {'CHARSET': 'UTF8'}, + 'OPTIONS': { + ... your custom options here ... + } } } ``` @@ -262,9 +277,9 @@ To enable Discord integration, you will need to: `SITE_ROOT/integrations/add_discord/`. For example, if you are running a development server on `localhost:8000` then the redirect URI would be `http://localhost:8000/integrations/add_discord/` -* Look up your Discord app's Client ID and Client Secret. Add them - to your `hc/local_settings.py` file as `DISCORD_CLIENT_ID` and - `DISCORD_CLIENT_SECRET` fields. +* Look up your Discord app's Client ID and Client Secret. Put them + in `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` environment + variables. ### Pushover @@ -274,17 +289,17 @@ To enable Pushover integration, you will need to: * register a new application on https://pushover.net/apps/build * enable subscriptions in your application and make sure to enable the URL subscription type -* add the application token and subscription URL to `hc/local_settings.py`, as - `PUSHOVER_API_TOKEN` and `PUSHOVER_SUBSCRIPTION_URL` +* put the application token and the subscription URL in + `PUSHOVER_API_TOKEN` and `PUSHOVER_SUBSCRIPTION_URL` environment + variables ### Telegram * Create a Telegram bot by talking to the [BotFather](https://core.telegram.org/bots#6-botfather). Set the bot's name, description, user picture, and add a "/start" command. -* After creating the bot you will have the bot's name and token. Add them -to your `hc/local_settings.py` file as `TELEGRAM_BOT_NAME` and -`TELEGRAM_TOKEN` fields. +* After creating the bot you will have the bot's name and token. Put them +in `TELEGRAM_BOT_NAME` and `TELEGRAM_TOKEN` environment variables. * Run `settelegramwebhook` management command. This command tells Telegram where to forward channel messages by invoking Telegram's [setWebhook](https://core.telegram.org/bots/api#setwebhook) API call: diff --git a/hc/settings.py b/hc/settings.py index c3c5609a..0950a68f 100644 --- a/hc/settings.py +++ b/hc/settings.py @@ -1,13 +1,8 @@ """ -Django settings for hc project. - -Generated by 'django-admin startproject' using Django 1.8.2. - -For more information on this file, see -https://docs.djangoproject.com/en/1.8/topics/settings/ +Django settings for healthchecks project. For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.8/ref/settings/ +https://docs.djangoproject.com/en/2.1/ref/settings """ import os @@ -15,13 +10,29 @@ import warnings BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -HOST = "localhost" -SECRET_KEY = "---" -DEBUG = True -ALLOWED_HOSTS = [] -DEFAULT_FROM_EMAIL = 'healthchecks@example.org' -USE_PAYMENTS = False -REGISTRATION_OPEN = True + +def envbool(s, default): + v = os.getenv(s, default=default) + if v not in ("", "True", "False"): + msg = "Unexpected value %s=%s, use 'True' or 'False'" % (s, v) + raise Exception(msg) + return v == "True" + + +def envint(s, default): + v = os.getenv(s, default) + if v == "None": + return None + + return int(v) + + +SECRET_KEY = os.getenv("SECRET_KEY", "---") +DEBUG = envbool("DEBUG", "True") +ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "*").split(",") +DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "healthchecks@example.org") +USE_PAYMENTS = envbool("DEBUG", "False") +REGISTRATION_OPEN = envbool("DEBUG", "True") INSTALLED_APPS = ( @@ -90,28 +101,35 @@ DATABASES = { # You can switch database engine to postgres or mysql using environment # variable 'DB'. Travis CI does this. -if os.environ.get("DB") == "postgres": +if os.getenv("DB") == "postgres": DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', - 'USER': 'postgres', - 'NAME': 'hc', - 'TEST': {'CHARSET': 'UTF8'} + 'HOST': os.getenv("DB_HOST", ""), + 'PORT': os.getenv("DB_PORT", ""), + 'NAME': os.getenv("DB_NAME", "hc"), + 'USER': os.getenv("DB_USER", "postgres"), + 'PASSWORD': os.getenv("DB_PASSWORD", ""), + 'CONN_MAX_AGE': envint("DB_CONN_MAX_AGE", "0"), + 'TEST': {'CHARSET': 'UTF8'}, + 'OPTIONS': { + "sslmode": os.getenv("DB_SSLMODE", "prefer"), + "target_session_attrs": os.getenv("DB_TARGET_SESSION_ATTRS", "read-write") + } } } -if os.environ.get("DB") == "mysql": +if os.getenv("DB") == "mysql": DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', - 'USER': 'root', - 'NAME': 'hc', + 'NAME': os.getenv("DB_NAME", "hc"), + 'USER': os.getenv("DB_USER", "root"), + 'PASSWORD': os.getenv("DB_PASSWORD", ""), 'TEST': {'CHARSET': 'UTF8'} } } -LANGUAGE_CODE = 'en-us' - TIME_ZONE = 'UTC' USE_I18N = True @@ -120,10 +138,11 @@ USE_L10N = True USE_TZ = True -SITE_ROOT = "http://localhost:8000" -SITE_NAME = MASTER_BADGE_LABEL = "Mychecks" -PING_ENDPOINT = SITE_ROOT + "/ping/" -PING_EMAIL_DOMAIN = HOST +SITE_ROOT = os.getenv("SITE_ROOT", "http://localhost:8000") +SITE_NAME = os.getenv("SITE_NAME", "Mychecks") +MASTER_BADGE_LABEL = os.getenv("MASTER_BADGE_LABEL", SITE_NAME) +PING_ENDPOINT = os.getenv("PING_ENDPOINT", SITE_ROOT + "/ping/") +PING_EMAIL_DOMAIN = os.getenv("PING_EMAIL_DOMAIN", "localhost") STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] STATIC_ROOT = os.path.join(BASE_DIR, 'static-collected') @@ -135,42 +154,42 @@ STATICFILES_FINDERS = ( COMPRESS_OFFLINE = True COMPRESS_CSS_HASHING_METHOD = "content" -# Discord integration -- override these in local_settings -DISCORD_CLIENT_ID = None -DISCORD_CLIENT_SECRET = None +# Discord integration +DISCORD_CLIENT_ID = os.getenv("DISCORD_CLIENT_ID") +DISCORD_CLIENT_SECRET = os.getenv("DISCORD_CLIENT_SECRET") -# Slack integration -- override these in local_settings -SLACK_CLIENT_ID = None -SLACK_CLIENT_SECRET = None +# Slack integration +SLACK_CLIENT_ID = os.getenv("SLACK_CLIENT_ID") +SLACK_CLIENT_SECRET = os.getenv("SLACK_CLIENT_SECRET") -# Pushover integration -- override these in local_settings -PUSHOVER_API_TOKEN = None -PUSHOVER_SUBSCRIPTION_URL = None -PUSHOVER_EMERGENCY_RETRY_DELAY = 300 -PUSHOVER_EMERGENCY_EXPIRATION = 86400 +# Pushover integration +PUSHOVER_API_TOKEN = os.getenv("PUSHOVER_API_TOKEN") +PUSHOVER_SUBSCRIPTION_URL = os.getenv("PUSHOVER_SUBSCRIPTION_URL") +PUSHOVER_EMERGENCY_RETRY_DELAY = int(os.getenv("PUSHOVER_EMERGENCY_RETRY_DELAY", "300")) +PUSHOVER_EMERGENCY_EXPIRATION = int(os.getenv("PUSHOVER_EMERGENCY_EXPIRATION", "86400")) -# Pushbullet integration -- override these in local_settings -PUSHBULLET_CLIENT_ID = None -PUSHBULLET_CLIENT_SECRET = None +# Pushbullet integration +PUSHBULLET_CLIENT_ID = os.getenv("PUSHBULLET_CLIENT_ID") +PUSHBULLET_CLIENT_SECRET = os.getenv("PUSHBULLET_CLIENT_SECRET") # Telegram integration -- override in local_settings.py -TELEGRAM_BOT_NAME = "ExampleBot" -TELEGRAM_TOKEN = None +TELEGRAM_BOT_NAME = os.getenv("TELEGRAM_BOT_NAME", "ExampleBot") +TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN") -# SMS (Twilio) integration -- override in local_settings.py -TWILIO_ACCOUNT = None -TWILIO_AUTH = None -TWILIO_FROM = None +# SMS (Twilio) integration +TWILIO_ACCOUNT = os.getenv("TWILIO_ACCOUNT") +TWILIO_AUTH = os.getenv("TWILIO_AUTH") +TWILIO_FROM = os.getenv("TWILIO_FROM") # PagerDuty -PD_VENDOR_KEY = None +PD_VENDOR_KEY = os.getenv("PD_VENDOR_KEY") # Zendesk -ZENDESK_CLIENT_ID = None -ZENDESK_CLIENT_SECRET = None +ZENDESK_CLIENT_ID = os.getenv("ZENDESK_CLIENT_ID") +ZENDESK_CLIENT_SECRET = os.getenv("ZENDESK_CLIENT_ID") # Trello -TRELLO_APP_KEY = None +TRELLO_APP_KEY = os.getenv("TRELLO_APP_KEY") if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")): from .local_settings import *