diff --git a/awx/lib/site-packages/README b/awx/lib/site-packages/README index 6ea4b6aa9d..58bf49acbc 100644 --- a/awx/lib/site-packages/README +++ b/awx/lib/site-packages/README @@ -18,6 +18,7 @@ django-auth-ldap==1.1.6 (django_auth_ldap/*) django-celery==3.1.1 (djcelery/*) django-extensions==1.2.5 (django_extensions/*) django-jsonfield==0.9.11 (jsonfield/*) +django-split-settings==0.1.1 (split_settings/*) django-taggit==0.10 (taggit/*) djangorestframework==2.3.8 (rest_framework/*) httplib2==0.8 (httplib2/*) diff --git a/awx/lib/site-packages/split_settings/__init__.py b/awx/lib/site-packages/split_settings/__init__.py new file mode 100644 index 0000000000..df9144c549 --- /dev/null +++ b/awx/lib/site-packages/split_settings/__init__.py @@ -0,0 +1 @@ +__version__ = '0.1.1' diff --git a/awx/lib/site-packages/split_settings/tools.py b/awx/lib/site-packages/split_settings/tools.py new file mode 100644 index 0000000000..e7ccd78033 --- /dev/null +++ b/awx/lib/site-packages/split_settings/tools.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +import glob +import os +import sys +import types + +class optional(str): + """Wrap a file path with this class to mark it as optional. + + Optional paths don't raise an IOError if file is not found. + """ + pass + +def include(*args, **kwargs): + """Used for including Django project settings from multiple files. + + Note: Expects to get ``scope=locals()`` as a keyword argument. + + Usage:: + + from split_settings.tools import optional, include + + include( + 'components/base.py', + 'components/database.py', + optional('local_settings.py'), + + scope=locals() + ) + + Parameters: + *args: File paths (``glob``-compatible wildcards can be used) + scope: The context for the settings, should always be ``locals()`` + Raises: + IOError: if a required settings file is not found + + """ + scope = kwargs.pop("scope") + including_file = scope.get('__included_file__', + scope['__file__'].rstrip('c')) + confpath = os.path.dirname(including_file) + for conffile in args: + saved_included_file = scope.get('__included_file__') + pattern = os.path.join(confpath, conffile) + + # find files per pattern, raise an error if not found (unless file is + # optional) + files_to_include = glob.glob(pattern) + if not files_to_include and not isinstance(conffile, optional): + raise IOError('No such file: %s' % pattern) + + for included_file in files_to_include: + scope['__included_file__'] = included_file + execfile(included_file, {}, scope) + + # add dummy modules to sys.modules to make runserver autoreload + # work with settings components + modulename = ('_split_settings.%s' + % conffile[:conffile.rfind('.')].replace('/', '.')) + module = types.ModuleType(modulename) + module.__file__ = included_file + sys.modules[modulename] = module + if saved_included_file: + scope['__included_file__'] = saved_included_file + elif '__included_file__' in scope: + del scope['__included_file__'] diff --git a/awx/settings/development.py b/awx/settings/development.py index 28e2ebbfaf..38863dd493 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -2,21 +2,25 @@ # All Rights Reserved. # Development settings for AWX project. + +# Python +import sys +import traceback + +# Django Split Settings +from split_settings.tools import optional, include + +# Load default settings. from defaults import * -# If a local_settings.py file is present in awx/settings/, use it to override +# If any local_*.py files are present in awx/settings/, use them to override # default settings for development. If not present, we can still run using # only the defaults. try: - local_settings_file = os.path.join(os.path.dirname(__file__), - 'local_settings.py') - execfile(local_settings_file) - # Hack so that the autoreload will detect changes to local_settings.py. - class dummymodule(str): - __file__ = property(lambda self: self) - sys.modules['local_settings'] = dummymodule(local_settings_file) -except IOError, e: - from django.core.exceptions import ImproperlyConfigured - if os.path.exists(local_settings_file): - msg = 'Unable to load %s: %s' % (local_settings_file, str(e)) - raise ImproperlyConfigured(msg) + include( + optional('local_*.py'), + scope=locals(), + ) +except ImportError: + traceback.print_exc() + sys.exit(1) diff --git a/awx/settings/production.py b/awx/settings/production.py index d4b402095f..e9df966892 100644 --- a/awx/settings/production.py +++ b/awx/settings/production.py @@ -2,6 +2,15 @@ # All Rights Reserved. # Production settings for AWX project. + +# Python +import sys +import traceback + +# Django Split Settings +from split_settings.tools import optional, include + +# Load default settings. from defaults import * DEBUG = False @@ -30,19 +39,34 @@ INTERNAL_API_URL = 'http://127.0.0.1:80' # This directory should not be web-accessible JOBOUTPUT_ROOT = '/var/lib/awx/job_status/' +# Load settings from any .py files in the global conf.d directory specified in +# the environment, defaulting to /etc/awx/conf.d/. +settings_dir = os.environ.get('AWX_SETTINGS_DIR', '/etc/awx/conf.d/') +settings_files = os.path.join(settings_dir, '*.py') + # Load remaining settings from the global settings file specified in the # environment, defaulting to /etc/awx/settings.py. settings_file = os.environ.get('AWX_SETTINGS_FILE', '/etc/awx/settings.py') + +# Attempt to load settings from /etc/awx/settings.py first, followed by +# /etc/awx/conf.d/*.py. try: - execfile(settings_file) -except IOError, e: + include( + settings_file, + optional(settings_files), + scope=locals(), + ) +except ImportError: + traceback.print_exc() + sys.exit(1) +except IOError: from django.core.exceptions import ImproperlyConfigured - if not os.path.exists(settings_file): - msg = 'No AWX configuration found at %s.' % settings_file + included_file = locals().get('__included_file__', '') + if (not included_file or included_file == settings_file) and not os.path.exists(settings_file): if 'AWX_SETTINGS_FILE' not in os.environ: + msg = 'No AWX configuration found at %s.' % settings_file msg += '\nDefine the AWX_SETTINGS_FILE environment variable to ' msg += 'specify an alternate path.' - else: - msg = 'Unable to load %s: %s' % (settings_file, str(e)) - raise ImproperlyConfigured(msg) + raise ImproperlyConfigured(msg) + raise