From 23ad4066a073a14969395d75fe542f33fcf46846 Mon Sep 17 00:00:00 2001 From: DinoBektesevic Date: Sun, 4 Apr 2021 21:33:10 -0700 Subject: [PATCH 1/7] Add a generic configration class. Configuration class that maps YAML dictionaries to instance attributes. Should be flexible enough when in the future we transition to PostgreSQL DBMS. Removed security key logic out of settings.py. --- trail/trail/config.py | 93 +++++++++++++++++++++++++++++++++++++++++ trail/trail/settings.py | 16 ++++--- 2 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 trail/trail/config.py diff --git a/trail/trail/config.py b/trail/trail/config.py new file mode 100644 index 0000000..a093598 --- /dev/null +++ b/trail/trail/config.py @@ -0,0 +1,93 @@ +import os +import stat + +import yaml + + +__all__ = ["CONF_FILE_ENVVAR", "CONF_FILE_PATH", "Config"] + + +CONF_FILE_ENVVAR = "TARILBLAZER_CONFIG" +"""Default name of the environmental variable that contains the path to the +configuration file. When the env var does not exist, configuration is assumed +to exist at its default location (see ``CONF_FILE_PATH``).""" + +CONF_FILE_PATH = "~/.trail/config.yaml" +"""Default path at which it is expected the config file can be found. Will be +ignored if ``CONF_FILE_ENVVAR`` env var exists.""" + + +class Config(): + """Represents a general YAML configuration file, with keys being mapped to + attributes. + + Parameters + ---------- + confDict : `dict` + Dictionary whose keys will be mapped to attributes of the class. + """ + def __init__(self, confDict): + self._keys = [] + self._subConfs = [] + self._recurseDownDicts(confDict) + + def _recurseDownDicts(self, confDict): + """Recursively walks the dictionary keys and values and maps keys to + instance attributes. + + Parameters + ---------- + confDict : `dict` + Dictionary whose keys will be mapped to attributes of the class. + """ + for key, val in confDict.items(): + if isinstance(val, dict): + self._subConfs.append(key) + setattr(self, key, Config(val)) + else: + self._keys.append(key) + setattr(self, key, val) + + def __repr__(self): + reprStr = "Config(" + + for key in self._subConfs: + reprStr += f"{key}={getattr(self, key)}, " + + for key in self._keys: + reprStr += key + ", " + reprStr = reprStr[:-2] + + return reprStr+")" + + @classmethod + def fromYaml(cls, filePath=None): + """Create a new Config instance from a YAML file. + + Parameters + ---------- + filePath : `str` or `None`, Optional + A file path to the YAML configuration. When not specified, first + the ``CONF_FILE_ENVVAR`` is used. If it doesn't exist the + ``CONF_FILE_PATH`` is used. + """ + # resolve config file path + if filePath is None: + if CONF_FILE_ENVVAR in os.environ: + filePath = os.path.expanduser(os.environ[CONF_FILE_ENVVAR]) + else: + filePath = os.path.expanduser(CONF_FILE_PATH) + + # make sure file exists and its permissions are at 600 or more + if not os.path.isfile(filePath): + raise FileNotFoundError(f"No configuration file found: {filePath}") + + mode = os.stat(filePath).st_mode + if mode & (stat.S_IRWXG | stat.S_IRWXO) != 0: + raise PermissionError(f"Configuration file {filePath} has " + f"incorrect permissions: {mode:o}") + + with open(filePath, 'r') as stream: + confDict = yaml.safe_load(stream) + + return cls(confDict) diff --git a/trail/trail/settings.py b/trail/trail/settings.py index ed399ce..45ab079 100644 --- a/trail/trail/settings.py +++ b/trail/trail/settings.py @@ -11,7 +11,12 @@ """ from pathlib import Path -import os + +from .config import Config + +# Trailblazer configuration is stored in a YAML file, usually at +# ~/.trail/config.yaml. See config.py for details. +config = Config.fromYaml() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -24,14 +29,7 @@ # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -home = str(Path.home()) -try: - secret_key_file = os.path.join(home, '.trail/secret_key.txt') - with open(secret_key_file) as f: - SECRET_KEY = f.read().strip() -except FileNotFoundError: - print('Unable to find secret key file {secret_key_file}.') - +SECRET_KEY = config.settings.secret_key # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True From e1a03537ae272f3efedafc6799a2195c1e7bfc2a Mon Sep 17 00:00:00 2001 From: DinoBektesevic Date: Sun, 4 Apr 2021 22:34:24 -0700 Subject: [PATCH 2/7] Add tests for Config class. Add equality comparison operator for Config. Make a test directory for trails app, add example config files. Write tests. --- trail/trail/config.py | 18 ++++- trail/trail/tests/__init__.py | 0 .../trail/tests/config/badPermissionConf.yaml | 0 trail/trail/tests/config/conf.yaml | 11 ++++ trail/trail/tests/test_config.py | 66 +++++++++++++++++++ 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 trail/trail/tests/__init__.py create mode 100644 trail/trail/tests/config/badPermissionConf.yaml create mode 100644 trail/trail/tests/config/conf.yaml create mode 100644 trail/trail/tests/test_config.py diff --git a/trail/trail/config.py b/trail/trail/config.py index a093598..39bed59 100644 --- a/trail/trail/config.py +++ b/trail/trail/config.py @@ -33,7 +33,7 @@ def __init__(self, confDict): def _recurseDownDicts(self, confDict): """Recursively walks the dictionary keys and values and maps keys to - instance attributes. + instance attributes. Parameters ---------- @@ -60,6 +60,22 @@ def __repr__(self): return reprStr+")" + + def __eq__(self, other): + equal = True + + for key, subConf in zip(self._keys, self._subConfs): + try: + equal = equal and getattr(self, key) == getattr(other, key) + equal = equal and getattr(self, subConf) == getattr(other, subConf) + except AttributeError: + # other does not have a key, but self has - not equal + # or other does not have a subConf, but self has - not equal + return False + + return equal + + @classmethod def fromYaml(cls, filePath=None): """Create a new Config instance from a YAML file. diff --git a/trail/trail/tests/__init__.py b/trail/trail/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/trail/trail/tests/config/badPermissionConf.yaml b/trail/trail/tests/config/badPermissionConf.yaml new file mode 100644 index 0000000..e69de29 diff --git a/trail/trail/tests/config/conf.yaml b/trail/trail/tests/config/conf.yaml new file mode 100644 index 0000000..5c76f34 --- /dev/null +++ b/trail/trail/tests/config/conf.yaml @@ -0,0 +1,11 @@ +settings: + secret_key: nonsense + static_root: ~/trail/static + media_root: ~/trail/media +db: + engine: django.db.backend.postgresql_psycopg2 + name: dbname + user: dbuser + password: dbpassword + host: dbhost.alala.com + port: 5432 diff --git a/trail/trail/tests/test_config.py b/trail/trail/tests/test_config.py new file mode 100644 index 0000000..04cc2a7 --- /dev/null +++ b/trail/trail/tests/test_config.py @@ -0,0 +1,66 @@ +import os + +from django.test import TestCase +import yaml + +import trail.config as ConfigModule +from trail.config import Config + + +TESTDIR = os.path.abspath(os.path.dirname(__file__)) + + +class ConfigTestCase(TestCase): + testConfigDir = os.path.join(TESTDIR, "config") + + def setUp(self): + self.badConf = os.path.join(self.testConfigDir, "badPermissionConf.yaml") + self.goodConf = os.path.join(self.testConfigDir, "conf.yaml") + self.noExists = os.path.join(self.testConfigDir, "noexist.yaml") + + def tearDown(self): + pass + + def testInstantiation(self): + + # Test 600 permissions + with self.assertRaises(PermissionError): + Config.fromYaml(self.badConf) + + # Test missing file + with self.assertRaises(FileNotFoundError): + Config.fromYaml(self.noExists) + + # Test that fromYaml and direct instantiation produce same result + # Test it's possible to instantiate without errors, test env var and + # global var default instantiations. + try: + conf1 = Config.fromYaml(self.goodConf) + except Exception as e: + self.fail(f"ConfigTestCase.testConfig conf1 failed with:\n{e}") + + with open(self.goodConf, 'r') as stream: + confDict = yaml.safe_load(stream) + try: + conf2 = Config(confDict) + except Exception as e: + self.fail(f"ConfigTestCase.testConfig conf2 failed with:\n{e}") + + self.assertEqual(conf1, conf2) + + ConfigModule.CONF_FILE_PATH = self.goodConf + try: + conf3 = Config.fromYaml() + except Exception as e: + self.fail(f"ConfigTestCase.testConfig conf3 failed with:\n{e}") + + self.assertEqual(conf2, conf3) + + # Switch to a different conf file to verify overriding with env var + # works as intended + os.environ[ConfigModule.CONF_FILE_ENVVAR] = self.badConf + with self.assertRaises(PermissionError): + conf4 = Config.fromYaml() + + + From 4c90136aa250f260faa1b3cbaf8f69e241622fe8 Mon Sep 17 00:00:00 2001 From: DinoBektesevic Date: Tue, 27 Apr 2021 17:05:37 -0700 Subject: [PATCH 3/7] Clarify intended use of Config class and its derivatives. Add a more explicitly named class for database configs. Rename config file to secrets since that's the intended use. Add the bit of functionality that isolates the desired keys from the entire yaml to simplify use. Update tests. --- trail/trail/config.py | 22 +++++++++++++++++++--- trail/trail/tests/test_config.py | 14 +++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/trail/trail/config.py b/trail/trail/config.py index 39bed59..68ecfb4 100644 --- a/trail/trail/config.py +++ b/trail/trail/config.py @@ -12,7 +12,7 @@ configuration file. When the env var does not exist, configuration is assumed to exist at its default location (see ``CONF_FILE_PATH``).""" -CONF_FILE_PATH = "~/.trail/config.yaml" +CONF_FILE_PATH = "~/.trail/secrets.yaml" """Default path at which it is expected the config file can be found. Will be ignored if ``CONF_FILE_ENVVAR`` env var exists.""" @@ -26,6 +26,8 @@ class Config(): confDict : `dict` Dictionary whose keys will be mapped to attributes of the class. """ + configKey = "*" + def __init__(self, confDict): self._keys = [] self._subConfs = [] @@ -40,6 +42,12 @@ def _recurseDownDicts(self, confDict): confDict : `dict` Dictionary whose keys will be mapped to attributes of the class. """ + if self.configKey != "*": + if self.configKey not in confDict: + raise ValueError(f"Required config key {self.configKey} does not " + "exist in the config dictionary.") + confDict = confDict[self.configKey] + for key, val in confDict.items(): if isinstance(val, dict): self._subConfs.append(key) @@ -49,7 +57,7 @@ def _recurseDownDicts(self, confDict): setattr(self, key, val) def __repr__(self): - reprStr = "Config(" + reprStr = f"{self.__class__.__name__}(" for key in self._subConfs: reprStr += f"{key}={getattr(self, key)}, " @@ -78,7 +86,10 @@ def __eq__(self, other): @classmethod def fromYaml(cls, filePath=None): - """Create a new Config instance from a YAML file. + """Create a new Config instance from a YAML file. By default will + look at location pointed to by the environmental variable named by + `CONF_FILE_ENVVAR`. If the env var is not set it will default to + location set by `CONF_FILE_PATH`. Parameters ---------- @@ -107,3 +118,8 @@ def fromYaml(cls, filePath=None): confDict = yaml.safe_load(stream) return cls(confDict) + + + +class DbAuth(Config): + configKey = 'db' diff --git a/trail/trail/tests/test_config.py b/trail/trail/tests/test_config.py index 04cc2a7..634f040 100644 --- a/trail/trail/tests/test_config.py +++ b/trail/trail/tests/test_config.py @@ -4,7 +4,7 @@ import yaml import trail.config as ConfigModule -from trail.config import Config +from trail.config import Config, DbAuth TESTDIR = os.path.abspath(os.path.dirname(__file__)) @@ -22,7 +22,6 @@ def tearDown(self): pass def testInstantiation(self): - # Test 600 permissions with self.assertRaises(PermissionError): Config.fromYaml(self.badConf) @@ -53,7 +52,7 @@ def testInstantiation(self): conf3 = Config.fromYaml() except Exception as e: self.fail(f"ConfigTestCase.testConfig conf3 failed with:\n{e}") - + self.assertEqual(conf2, conf3) # Switch to a different conf file to verify overriding with env var @@ -62,5 +61,14 @@ def testInstantiation(self): with self.assertRaises(PermissionError): conf4 = Config.fromYaml() + def testConfigKey(self): + Config.configKey = "noexists" + with self.assertRaises(ValueError): + Config.fromYaml(self.goodConf) + + Config.configKey = "db" + conf1 = Config.fromYaml(self.goodConf) + conf2 = DbAuth.fromYaml(self.goodConf) + self.assertEqual(conf1, conf2) From 741cdbfe79367fcf397b745aa45423bc98baee7f Mon Sep 17 00:00:00 2001 From: DinoBektesevic Date: Wed, 28 Apr 2021 13:47:51 -0700 Subject: [PATCH 4/7] Add AWS Secrets Manager to Config. Add secrets resolution via AWS Secrets manager for simple and multi-keyed secrets. Add tests. Add documentation. --- trail/trail/config.py | 83 +++++++++++++++++--- trail/trail/settings.py | 10 +-- trail/trail/tests/config/awsSecretsConf.yaml | 2 + trail/trail/tests/test_config.py | 47 ++++++++++- 4 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 trail/trail/tests/config/awsSecretsConf.yaml diff --git a/trail/trail/config.py b/trail/trail/config.py index 68ecfb4..c3f2f4e 100644 --- a/trail/trail/config.py +++ b/trail/trail/config.py @@ -2,6 +2,7 @@ import stat import yaml +import boto3 __all__ = ["CONF_FILE_ENVVAR", "CONF_FILE_PATH", "Config"] @@ -19,43 +20,95 @@ class Config(): """Represents a general YAML configuration file, with keys being mapped to - attributes. + attributes. Optionally resolving existing secrets via AWS Secrets Manager. Parameters ---------- confDict : `dict` Dictionary whose keys will be mapped to attributes of the class. + useAwsSecrets : `bool`, optional + Resolve secrets using AWS Secrets manager. False by default. + awsRegion : `str`, optional + Region of the secret manager to use. Default: `us-west-2`. + + Notes + ----- + Secrets Manager can and will support any kind of string as a secret. For + RDS it will tests showed that secrets are stored as a JSON key-value string + pairs (i.e. output looks like a ``str(dict)``). This presents 3 different + scenarios when keys get resolved and set as Config attributes: + 1) resolve a secret key into multiple keys and insert them, replacing the + secret key with the recieved key-value pairs; + 2) resolve a secret and insert under original key, when returned secrets + are simple strings so the name of the secret is replaced with the secret + itself; + 3) and insert a key-value pair named in the YAML config file. """ + configKey = "*" + """Key which is read to create a config, the value `*` selects all keys.""" + + secretsKeys = [] + """Specifies which keys are to be resolved as secrets.""" - def __init__(self, confDict): + def __init__(self, confDict, useAwsSecrets=False, awsRegion="us-west-2"): self._keys = [] self._subConfs = [] - self._recurseDownDicts(confDict) + self._recurseDownDicts(confDict, useAwsSecrets, awsRegion=awsRegion) - def _recurseDownDicts(self, confDict): + def _recurseDownDicts(self, confDict, useAwsSecrets, awsRegion): """Recursively walks the dictionary keys and values and maps keys to - instance attributes. + instance attributes, resolving any existing secrets along the way. Parameters ---------- confDict : `dict` Dictionary whose keys will be mapped to attributes of the class. + useAwsSecrets : `bool`, optional + Resolve secrets using AWS Secrets manager. False by default. + awsRegion : `str`, optional + Region of the secret manager to use. Default: `us-west-2`. """ if self.configKey != "*": if self.configKey not in confDict: - raise ValueError(f"Required config key {self.configKey} does not " - "exist in the config dictionary.") + raise ValueError(f"Required config key {self.configKey} does " + "not exist in the config dictionary.") confDict = confDict[self.configKey] + # if a region is set in the config use it, otherwise use the default + region = confDict.get("aws-region", awsRegion) + for key, val in confDict.items(): if isinstance(val, dict): self._subConfs.append(key) setattr(self, key, Config(val)) else: + # of course this is now ugly... + if useAwsSecrets and key in self.secretsKeys: + secrets = self._parseAwsSecrets(val, region) + if isinstance(secrets, dict): + # scenario 1, replacing key with many + for secretkey, secretval in secrets.items(): + if secretkey not in self._keys: + self._keys.append(secretkey) + setattr(self, secretkey, secretval) + # skip inserting the replaced key + continue + else: + # scenario 2, resolve simple secret as key + val = secrets + # scenario 2 or 3, insert key-value pair, resolving secrets self._keys.append(key) setattr(self, key, val) + @staticmethod + def _parseAwsSecrets(name, region): + smClient = boto3.client("secretsmanager", region_name=region) + secretString = smClient.get_secret_value(SecretId=name)["SecretString"] + # JSON is like YAML, right? + return yaml.safe_load(secretString) + + def __repr__(self): reprStr = f"{self.__class__.__name__}(" @@ -83,9 +136,8 @@ def __eq__(self, other): return equal - @classmethod - def fromYaml(cls, filePath=None): + def fromYaml(cls, filePath=None, useAwsSecrets=False, awsRegion="us-west-2"): """Create a new Config instance from a YAML file. By default will look at location pointed to by the environmental variable named by `CONF_FILE_ENVVAR`. If the env var is not set it will default to @@ -97,6 +149,10 @@ def fromYaml(cls, filePath=None): A file path to the YAML configuration. When not specified, first the ``CONF_FILE_ENVVAR`` is used. If it doesn't exist the ``CONF_FILE_PATH`` is used. + useAwsSecrets : `bool`, optional + Resolve secrets using AWS Secrets manager. False by default. + awsRegion : `str`, optional + Region of the secret manager to use. Default: `us-west-2`. """ # resolve config file path if filePath is None: @@ -117,9 +173,14 @@ def fromYaml(cls, filePath=None): with open(filePath, 'r') as stream: confDict = yaml.safe_load(stream) - return cls(confDict) - + return cls(confDict, useAwsSecrets, awsRegion) class DbAuth(Config): configKey = 'db' + secretsKeys = ["secret_name", ] + + +class SiteConfig(Config): + configKey = 'settings' + secretsKeys = ["secret_key", ] diff --git a/trail/trail/settings.py b/trail/trail/settings.py index 45ab079..c38255f 100644 --- a/trail/trail/settings.py +++ b/trail/trail/settings.py @@ -11,12 +11,12 @@ """ from pathlib import Path +import os -from .config import Config +from .config import DbAuth, SiteConfig -# Trailblazer configuration is stored in a YAML file, usually at -# ~/.trail/config.yaml. See config.py for details. -config = Config.fromYaml() +siteConfig = SiteConfig.fromYaml() +dbConfig = DbAuth.fromYaml() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -29,7 +29,7 @@ # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = config.settings.secret_key +SECRET_KEY = siteConfig.secret_key # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True diff --git a/trail/trail/tests/config/awsSecretsConf.yaml b/trail/trail/tests/config/awsSecretsConf.yaml new file mode 100644 index 0000000..8fe51a2 --- /dev/null +++ b/trail/trail/tests/config/awsSecretsConf.yaml @@ -0,0 +1,2 @@ +db: + secret_name: "db-secret" diff --git a/trail/trail/tests/test_config.py b/trail/trail/tests/test_config.py index 634f040..2783da0 100644 --- a/trail/trail/tests/test_config.py +++ b/trail/trail/tests/test_config.py @@ -1,10 +1,13 @@ import os from django.test import TestCase +from moto import mock_secretsmanager +import boto3 import yaml + import trail.config as ConfigModule -from trail.config import Config, DbAuth +from trail.config import Config, DbAuth, SiteConfig TESTDIR = os.path.abspath(os.path.dirname(__file__)) @@ -66,9 +69,51 @@ def testConfigKey(self): with self.assertRaises(ValueError): Config.fromYaml(self.goodConf) + # this is a bit silly I think because it doesn't test correctness? Config.configKey = "db" conf1 = Config.fromYaml(self.goodConf) conf2 = DbAuth.fromYaml(self.goodConf) self.assertEqual(conf1, conf2) +class AwsSecretsTestCase(TestCase): + testConfigDir = os.path.join(TESTDIR, "config") + + def setUp(self): + self.goodConf = os.path.join(self.testConfigDir, "conf.yaml") + self.awsSecretsConf = os.path.join(self.testConfigDir, "awsSecretsConf.yaml") + + @mock_secretsmanager + def testSimpleAwsSecrets(self): + smClient = boto3.client("secretsmanager", region_name="us-west-2") + smClient.create_secret(Name="nonsense", SecretString="test-secret-key") + + conf = SiteConfig.fromYaml(self.goodConf, useAwsSecrets=True) + + self.assertEqual(conf.secret_key, "test-secret-key") + + @mock_secretsmanager + def testMultiKeyedSecret(self): + multiKeyedSecret = { + "engine": "postgresql", + "name": "dbname", + "user": "dbuser", + "password": "dbpassword", + "host": "dbhost.alala.com", + "port": 5432, + } + smClient = boto3.client("secretsmanager", region_name="us-west-2") + smClient.create_secret(Name="db-secret", SecretString=str(multiKeyedSecret)) + + conf = DbAuth.fromYaml(self.awsSecretsConf, useAwsSecrets=True) + + # verify the secret_key was expanded + for key, val in multiKeyedSecret.items(): + self.assertEqual(getattr(conf, key), val) + + # verify that the replaced key was not inserted + with self.assertRaises(AttributeError): + conf.secret_name + + + From d9c8327ac44d30b8d04d76cf59e33364eb6ce991 Mon Sep 17 00:00:00 2001 From: DinoBektesevic Date: Mon, 5 Apr 2021 15:13:05 -0700 Subject: [PATCH 5/7] Move default database to PostgreSQL. --- trail/trail/settings.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/trail/trail/settings.py b/trail/trail/settings.py index c38255f..0ad5ea6 100644 --- a/trail/trail/settings.py +++ b/trail/trail/settings.py @@ -88,12 +88,17 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': REPO_DIR / 'db.sqlite3', - } + 'ENGINE': config.db.engine, + 'NAME' : config.db.name, + 'USER' : config.db.user, + 'PASSWORD' : config.db.password, + 'HOST' : config.db.host, + 'PORT' : config.db.port + } } + # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators From 68580201834916a61a62d00822628d451b247ef1 Mon Sep 17 00:00:00 2001 From: DinoBektesevic Date: Mon, 5 Apr 2021 15:16:17 -0700 Subject: [PATCH 6/7] Update requirements. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 85a7681..eba7fd0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ MarkupSafe==1.1.1 matplotlib==3.3.4 numpy==1.20.1 Pillow==8.1.2 +psycopg2==2.8.6 pyerfa==1.7.2 pyparsing==2.4.7 python-dateutil==2.8.1 From 022a5aeccfd6da70b4722c2742c8b15c23ee3dac Mon Sep 17 00:00:00 2001 From: DinoBektesevic Date: Wed, 28 Apr 2021 13:55:40 -0700 Subject: [PATCH 7/7] Rebase on config and fix settings.py --- trail/trail/settings.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/trail/trail/settings.py b/trail/trail/settings.py index 0ad5ea6..ccbb9b5 100644 --- a/trail/trail/settings.py +++ b/trail/trail/settings.py @@ -88,12 +88,12 @@ DATABASES = { 'default': { - 'ENGINE': config.db.engine, - 'NAME' : config.db.name, - 'USER' : config.db.user, - 'PASSWORD' : config.db.password, - 'HOST' : config.db.host, - 'PORT' : config.db.port + 'ENGINE': dbConfig.engine, + 'NAME' : dbConfig.name, + 'USER' : dbConfig.user, + 'PASSWORD' : dbConfig.password, + 'HOST' : dbConfig.host, + 'PORT' : dbConfig.port } }