diff --git a/.coveragerc b/.coveragerc index 086c99d2c80e..caafe9b6df90 100644 --- a/.coveragerc +++ b/.coveragerc @@ -19,6 +19,8 @@ exclude_also = branch = True omit = awx/main/migrations/* + awx/settings/defaults.py + awx/settings/*_defaults.py source = . source_pkgs = diff --git a/awx/__init__.py b/awx/__init__.py index 6b2f809c3027..59cccd5d8b87 100644 --- a/awx/__init__.py +++ b/awx/__init__.py @@ -62,7 +62,8 @@ def version_file(): def prepare_env(): # Update the default settings environment variable based on current mode. - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'awx.settings.%s' % MODE) + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'awx.settings') + os.environ.setdefault('AWX_MODE', MODE) # Hide DeprecationWarnings when running in production. Need to first load # settings to apply our filter after Django's own warnings filter. from django.conf import settings diff --git a/awx/api/generics.py b/awx/api/generics.py index 71c42f6b4d84..207799b27d06 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -161,7 +161,7 @@ def get_view_description(view, html=False): def get_default_schema(): - if settings.SETTINGS_MODULE == 'awx.settings.development': + if settings.DYNACONF.is_development_mode: from awx.api.swagger import schema_view return schema_view diff --git a/awx/main/tests/unit/test_settings.py b/awx/main/tests/unit/test_settings.py index 7ff2e3f4abf6..acc376c15cb4 100644 --- a/awx/main/tests/unit/test_settings.py +++ b/awx/main/tests/unit/test_settings.py @@ -1,6 +1,3 @@ -from split_settings.tools import include - - LOCAL_SETTINGS = ( 'ALLOWED_HOSTS', 'BROADCAST_WEBSOCKET_PORT', @@ -16,13 +13,14 @@ def test_postprocess_auth_basic_enabled(): - locals().update({'__file__': __file__}) + """The final loaded settings should have basic auth enabled.""" + from awx.settings import REST_FRAMEWORK - include('../../../settings/defaults.py', scope=locals()) - assert 'awx.api.authentication.LoggedBasicAuthentication' in locals()['REST_FRAMEWORK']['DEFAULT_AUTHENTICATION_CLASSES'] + assert 'awx.api.authentication.LoggedBasicAuthentication' in REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] def test_default_settings(): + """Ensure that all default settings are present in the snapshot.""" from django.conf import settings for k in dir(settings): @@ -31,3 +29,43 @@ def test_default_settings(): default_val = getattr(settings.default_settings, k, None) snapshot_val = settings.DEFAULTS_SNAPSHOT[k] assert default_val == snapshot_val, f'Setting for {k} does not match shapshot:\nsnapshot: {snapshot_val}\ndefault: {default_val}' + + +def test_django_conf_settings_is_awx_settings(): + """Ensure that the settings loaded from dynaconf are the same as the settings delivered to django.""" + from django.conf import settings + from awx.settings import REST_FRAMEWORK + + assert settings.REST_FRAMEWORK == REST_FRAMEWORK + + +def test_dynaconf_is_awx_settings(): + """Ensure that the settings loaded from dynaconf are the same as the settings delivered to django.""" + from django.conf import settings + from awx.settings import REST_FRAMEWORK + + assert settings.DYNACONF.REST_FRAMEWORK == REST_FRAMEWORK + + +def test_development_settings_can_be_directly_imported(monkeypatch): + """Ensure that the development settings can be directly imported.""" + monkeypatch.setenv('AWX_MODE', 'development') + from django.conf import settings + from awx.settings.development import REST_FRAMEWORK + from awx.settings.development import DEBUG # actually set on defaults.py and not overridden in development.py + + assert settings.REST_FRAMEWORK == REST_FRAMEWORK + assert DEBUG is True + + +def test_merge_application_name(): + """Ensure that the merge_application_name function works as expected.""" + from awx.settings.functions import merge_application_name + + settings = { + "DATABASES__default__ENGINE": "django.db.backends.postgresql", + "CLUSTER_HOST_ID": "test-cluster-host-id", + } + result = merge_application_name(settings)["DATABASES__default__OPTIONS__application_name"] + assert result.startswith("awx-") + assert "test-cluster" in result diff --git a/awx/settings/__init__.py b/awx/settings/__init__.py index e484e62be15d..641d442bbce5 100644 --- a/awx/settings/__init__.py +++ b/awx/settings/__init__.py @@ -1,2 +1,82 @@ # Copyright (c) 2015 Ansible, Inc. # All Rights Reserved. +import os +import copy +from ansible_base.lib.dynamic_config import ( + factory, + export, + load_envvars, + load_python_file_with_injected_context, + load_standard_settings_files, + toggle_feature_flags, +) +from .functions import ( + assert_production_settings, + merge_application_name, + add_backwards_compatibility, + load_extra_development_files, +) + +add_backwards_compatibility() + +# Create a the standard DYNACONF instance which will come with DAB defaults +# This loads defaults.py and environment specific file e.g: development_defaults.py +DYNACONF = factory( + __name__, + "AWX", + environments=("development", "production", "quiet", "kube"), + settings_files=["defaults.py"], +) + +# Store snapshot before loading any custom config file +DYNACONF.set( + "DEFAULTS_SNAPSHOT", + copy.deepcopy(DYNACONF.as_dict(internal=False)), + loader_identifier="awx.settings:DEFAULTS_SNAPSHOT", +) + +############################################################################################# +# Settings loaded before this point will be allowed to be overridden by the database settings +# Any settings loaded after this point will be marked as as a read_only database setting +############################################################################################# + +# Load extra settings files from the following directories +# /etc/tower/conf.d/ and /etc/tower/ +# this is the legacy location, kept for backwards compatibility +settings_dir = os.environ.get('AWX_SETTINGS_DIR', '/etc/tower/conf.d/') +settings_files_path = os.path.join(settings_dir, '*.py') +settings_file_path = os.environ.get('AWX_SETTINGS_FILE', '/etc/tower/settings.py') +load_python_file_with_injected_context(settings_files_path, settings=DYNACONF) +load_python_file_with_injected_context(settings_file_path, settings=DYNACONF) + +# Load extra settings files from the following directories +# /etc/ansible-automation-platform/{settings,flags,.secrets}.yaml +# and /etc/ansible-automation-platform/awx/{settings,flags,.secrets}.yaml +# this is the new standard location for all services +load_standard_settings_files(DYNACONF) + +# Load optional development only settings files +load_extra_development_files(DYNACONF) + +# Check at least one setting file has been loaded in production mode +assert_production_settings(DYNACONF, settings_dir, settings_file_path) + +# Load envvars at the end to allow them to override everything loaded so far +load_envvars(DYNACONF) + +# This must run after all custom settings are loaded +DYNACONF.update( + merge_application_name(DYNACONF), + loader_identifier="awx.settings:merge_application_name", + merge=True, +) + +# Toggle feature flags based on installer settings +DYNACONF.update( + toggle_feature_flags(DYNACONF), + loader_identifier="awx.settings:toggle_feature_flags", + merge=True, +) + +# Update django.conf.settings with DYNACONF values +export(__name__, DYNACONF) diff --git a/awx/settings/application_name.py b/awx/settings/application_name.py index ed76886c395e..ac7e40553e00 100644 --- a/awx/settings/application_name.py +++ b/awx/settings/application_name.py @@ -25,6 +25,7 @@ def get_application_name(CLUSTER_HOST_ID, function=''): def set_application_name(DATABASES, CLUSTER_HOST_ID, function=''): + """In place modification of DATABASES to set the application name for the connection.""" # If settings files were not properly passed DATABASES could be {} at which point we don't need to set the app name. if not DATABASES or 'default' not in DATABASES: return diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 1142109704f5..84f9995bc4a6 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -9,9 +9,6 @@ import socket from datetime import timedelta -from split_settings.tools import include - - DEBUG = True SQL_DEBUG = DEBUG @@ -1015,16 +1012,15 @@ } } - # django-ansible-base ANSIBLE_BASE_TEAM_MODEL = 'main.Team' ANSIBLE_BASE_ORGANIZATION_MODEL = 'main.Organization' ANSIBLE_BASE_RESOURCE_CONFIG_MODULE = 'awx.resource_api' ANSIBLE_BASE_PERMISSION_MODEL = 'main.Permission' -from ansible_base.lib import dynamic_config # noqa: E402 - -include(os.path.join(os.path.dirname(dynamic_config.__file__), 'dynamic_settings.py')) +# Defaults to be overridden by DAB +SPECTACULAR_SETTINGS = {} +OAUTH2_PROVIDER = {} # Add a postfix to the API URL patterns # example if set to '' API pattern will be /api diff --git a/awx/settings/development.py b/awx/settings/development.py index e4e58c7de27d..5b630c49841a 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -1,129 +1,13 @@ -# Copyright (c) 2015 Ansible, Inc. -# All Rights Reserved. - -# Development settings for AWX project. - -# Python +# This file exists for backwards compatibility only +# the current way of running AWX is to point settings to +# awx/settings/__init__.py as the entry point for the settings +# that is done by exporting: export DJANGO_SETTINGS_MODULE=awx.settings import os -import socket -import copy -import sys -import traceback - -# Centos-7 doesn't include the svg mime type -# /usr/lib64/python/mimetypes.py -import mimetypes - -# Django Split Settings -from split_settings.tools import optional, include - -# Load default settings. -from .defaults import * # NOQA - -# awx-manage shell_plus --notebook -NOTEBOOK_ARGUMENTS = ['--NotebookApp.token=', '--ip', '0.0.0.0', '--port', '9888', '--allow-root', '--no-browser'] - -# print SQL queries in shell_plus -SHELL_PLUS_PRINT_SQL = False - -# show colored logs in the dev environment -# to disable this, set `COLOR_LOGS = False` in awx/settings/local_settings.py -COLOR_LOGS = True -LOGGING['handlers']['console']['()'] = 'awx.main.utils.handlers.ColorHandler' # noqa - -ALLOWED_HOSTS = ['*'] - -mimetypes.add_type("image/svg+xml", ".svg", True) -mimetypes.add_type("image/svg+xml", ".svgz", True) - -# Disallow sending session cookies over insecure connections -SESSION_COOKIE_SECURE = False - -# Disallow sending csrf cookies over insecure connections -CSRF_COOKIE_SECURE = False - -# Disable Pendo on the UI for development/test. -# Note: This setting may be overridden by database settings. -PENDO_TRACKING_STATE = "off" -INSIGHTS_TRACKING_STATE = False - -# debug toolbar and swagger assume that requirements/requirements_dev.txt are installed - -INSTALLED_APPS += ['drf_yasg', 'debug_toolbar'] # NOQA - -MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware'] + MIDDLEWARE # NOQA - -DEBUG_TOOLBAR_CONFIG = {'ENABLE_STACKTRACES': True} - -# Configure a default UUID for development only. -SYSTEM_UUID = '00000000-0000-0000-0000-000000000000' -INSTALL_UUID = '00000000-0000-0000-0000-000000000000' - -# Ansible base virtualenv paths and enablement -# only used for deprecated fields and management commands for them -BASE_VENV_PATH = os.path.realpath("/var/lib/awx/venv") - -CLUSTER_HOST_ID = socket.gethostname() - -AWX_CALLBACK_PROFILE = True - -# this modifies FLAGS set by defaults -FLAGS['FEATURE_INDIRECT_NODE_COUNTING_ENABLED'] = [{'condition': 'boolean', 'value': True}] # noqa - -# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!================================= -# Disable normal scheduled/triggered task managers (DependencyManager, TaskManager, WorkflowManager). -# Allows user to trigger task managers directly for debugging and profiling purposes. -# Only works in combination with settings.SETTINGS_MODULE == 'awx.settings.development' -AWX_DISABLE_TASK_MANAGERS = False - -# Needed for launching runserver in debug mode -# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!================================= - -# Store a snapshot of default settings at this point before loading any -# customizable config files. -this_module = sys.modules[__name__] -local_vars = dir(this_module) -DEFAULTS_SNAPSHOT = {} # define after we save local_vars so we do not snapshot the snapshot -for setting in local_vars: - if setting.isupper(): - DEFAULTS_SNAPSHOT[setting] = copy.deepcopy(getattr(this_module, setting)) - -del local_vars # avoid temporary variables from showing up in dir(settings) -del this_module -# -############################################################################################### -# -# Any settings defined after this point will be marked as as a read_only database setting -# -################################################################################################ - -# If there is an `/etc/tower/settings.py`, include it. -# If there is a `/etc/tower/conf.d/*.py`, include them. -include(optional('/etc/tower/settings.py'), scope=locals()) -include(optional('/etc/tower/conf.d/*.py'), scope=locals()) - -# 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. -# this needs to stay at the bottom of this file -try: - if os.getenv('AWX_KUBE_DEVEL', False): - include(optional('development_kube.py'), scope=locals()) - else: - include(optional('local_*.py'), scope=locals()) -except ImportError: - traceback.print_exc() - sys.exit(1) - -# The below runs AFTER all of the custom settings are imported -# because conf.d files will define DATABASES and this should modify that -from .application_name import set_application_name -set_application_name(DATABASES, CLUSTER_HOST_ID) # NOQA +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings") +os.environ.setdefault("AWX_MODE", "development") -del set_application_name +from ansible_base.lib.dynamic_config import export +from . import DYNACONF # noqa -# Set the value of any feature flags that are defined in the local settings -for feature in list(FLAGS.keys()): # noqa: F405 - if feature in locals(): - FLAGS[feature][0]['value'] = locals()[feature] # noqa: F405 +export(__name__, DYNACONF) diff --git a/awx/settings/development_defaults.py b/awx/settings/development_defaults.py new file mode 100644 index 000000000000..bb726cb37228 --- /dev/null +++ b/awx/settings/development_defaults.py @@ -0,0 +1,76 @@ +# Copyright (c) 2015 Ansible, Inc. +# All Rights Reserved. + +# Development settings for AWX project. + +# Python +import os +import socket + +# Centos-7 doesn't include the svg mime type +# /usr/lib64/python/mimetypes.py +import mimetypes + +from dynaconf import post_hook + +# awx-manage shell_plus --notebook +NOTEBOOK_ARGUMENTS = ['--NotebookApp.token=', '--ip', '0.0.0.0', '--port', '9888', '--allow-root', '--no-browser'] + +# print SQL queries in shell_plus +SHELL_PLUS_PRINT_SQL = False + +# show colored logs in the dev environment +# to disable this, set `COLOR_LOGS = False` in awx/settings/local_settings.py +COLOR_LOGS = True +LOGGING__handlers__console = '@merge {"()": "awx.main.utils.handlers.ColorHandler"}' + +ALLOWED_HOSTS = ['*'] + +mimetypes.add_type("image/svg+xml", ".svg", True) +mimetypes.add_type("image/svg+xml", ".svgz", True) + +# Disallow sending session cookies over insecure connections +SESSION_COOKIE_SECURE = False + +# Disallow sending csrf cookies over insecure connections +CSRF_COOKIE_SECURE = False + +# Disable Pendo on the UI for development/test. +# Note: This setting may be overridden by database settings. +PENDO_TRACKING_STATE = "off" +INSIGHTS_TRACKING_STATE = False + +# debug toolbar and swagger assume that requirements/requirements_dev.txt are installed +INSTALLED_APPS = "@merge drf_yasg,debug_toolbar" +MIDDLEWARE = "@insert 0 debug_toolbar.middleware.DebugToolbarMiddleware" + +DEBUG_TOOLBAR_CONFIG = {'ENABLE_STACKTRACES': True} + +# Configure a default UUID for development only. +SYSTEM_UUID = '00000000-0000-0000-0000-000000000000' +INSTALL_UUID = '00000000-0000-0000-0000-000000000000' + +# Ansible base virtualenv paths and enablement +# only used for deprecated fields and management commands for them +BASE_VENV_PATH = os.path.realpath("/var/lib/awx/venv") + +CLUSTER_HOST_ID = socket.gethostname() + +AWX_CALLBACK_PROFILE = True + +# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!================================= +# Disable normal scheduled/triggered task managers (DependencyManager, TaskManager, WorkflowManager). +# Allows user to trigger task managers directly for debugging and profiling purposes. +# Only works in combination with settings.SETTINGS_MODULE == 'awx.settings.development' +AWX_DISABLE_TASK_MANAGERS = False + +# Needed for launching runserver in debug mode +# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!================================= + + +# This modifies FLAGS set by defaults, must be deferred to run later +@post_hook +def set_dev_flags(settings): + defaults_flags = settings.get("FLAGS", {}) + defaults_flags['FEATURE_INDIRECT_NODE_COUNTING_ENABLED'] = [{'condition': 'boolean', 'value': True}] + return {'FLAGS': defaults_flags} diff --git a/awx/settings/development_kube.py b/awx/settings/development_kube.py index c30a7fe025fe..e6ba6170c6d7 100644 --- a/awx/settings/development_kube.py +++ b/awx/settings/development_kube.py @@ -1,4 +1,13 @@ -BROADCAST_WEBSOCKET_SECRET = '🤖starscream🤖' -BROADCAST_WEBSOCKET_PORT = 8052 -BROADCAST_WEBSOCKET_VERIFY_CERT = False -BROADCAST_WEBSOCKET_PROTOCOL = 'http' +# This file exists for backwards compatibility only +# the current way of running AWX is to point settings to +# awx/settings/__init__.py as the entry point for the settings +# that is done by exporting: export DJANGO_SETTINGS_MODULE=awx.settings +import os + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings") +os.environ.setdefault("AWX_MODE", "development,kube") + +from ansible_base.lib.dynamic_config import export +from . import DYNACONF # noqa + +export(__name__, DYNACONF) diff --git a/awx/settings/development_quiet.py b/awx/settings/development_quiet.py index c47e78b69d86..5fea2756e908 100644 --- a/awx/settings/development_quiet.py +++ b/awx/settings/development_quiet.py @@ -1,15 +1,13 @@ -# Copyright (c) 2015 Ansible, Inc. -# All Rights Reserved. +# This file exists for backwards compatibility only +# the current way of running AWX is to point settings to +# awx/settings/__init__.py as the entry point for the settings +# that is done by exporting: export DJANGO_SETTINGS_MODULE=awx.settings +import os -# Development settings for AWX project, but with DEBUG disabled +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings") +os.environ.setdefault("AWX_MODE", "development,quiet") -# Load development settings. -from defaults import * # NOQA +from ansible_base.lib.dynamic_config import export +from . import DYNACONF # noqa -# Load development settings. -from development import * # NOQA - -# Disable capturing DEBUG -DEBUG = False -TEMPLATE_DEBUG = DEBUG -SQL_DEBUG = DEBUG +export(__name__, DYNACONF) diff --git a/awx/settings/functions.py b/awx/settings/functions.py new file mode 100644 index 000000000000..70be9befdbae --- /dev/null +++ b/awx/settings/functions.py @@ -0,0 +1,86 @@ +import os +from ansible_base.lib.dynamic_config import load_python_file_with_injected_context +from dynaconf import Dynaconf +from .application_name import get_application_name + + +def merge_application_name(settings): + """Return a dynaconf merge dict to set the application name for the connection.""" + data = {} + if "sqlite3" not in settings.get("DATABASES__default__ENGINE", ""): + data["DATABASES__default__OPTIONS__application_name"] = get_application_name(settings.get("CLUSTER_HOST_ID")) + return data + + +def add_backwards_compatibility(): + """Add backwards compatibility for AWX_MODE. + + Before dynaconf integration the usage of AWX settings was supported to be just + DJANGO_SETTINGS_MODULE=awx.settings.production or DJANGO_SETTINGS_MODULE=awx.settings.development + (development_quiet and development_kube were also supported). + + With dynaconf the DJANGO_SETTINGS_MODULE should be set always to "awx.settings" as the only entry point + for settings and then "AWX_MODE" can be set to any of production,development,quiet,kube + or a combination of them separated by comma. + + E.g: + + export DJANGO_SETTINGS_MODULE=awx.settings + export AWX_MODE=production + awx-manage [command] + dynaconf [command] + + If pointing `DJANGO_SETTINGS_MODULE` to `awx.settings.production` or `awx.settings.development` then + this function will set `AWX_MODE` to the correct value. + """ + django_settings_module = os.getenv("DJANGO_SETTINGS_MODULE", "awx.settings") + if django_settings_module == "awx.settings": + return + + current_mode = os.getenv("AWX_MODE", "") + for _module_name in ["development", "production", "development_quiet", "development_kube"]: + if django_settings_module == f"awx.settings.{_module_name}": + _mode = current_mode.split(",") + if "development_" in _module_name and "development" not in current_mode: + _mode.append("development") + _mode_fragment = _module_name.replace("development_", "") + if _mode_fragment not in _mode: + _mode.append(_mode_fragment) + os.environ["AWX_MODE"] = ",".join(_mode) + + +def load_extra_development_files(settings: Dynaconf): + """Load optional development only settings files.""" + if not settings.is_development_mode: + return + + if settings.get_environ("AWX_KUBE_DEVEL"): + load_python_file_with_injected_context("kube_defaults.py", settings=settings) + else: + load_python_file_with_injected_context("local_*.py", settings=settings) + + +def assert_production_settings(settings: Dynaconf, settings_dir: str, settings_file_path: str): # pragma: no cover + """Ensure at least one setting file has been loaded in production mode. + Current systems will require /etc/tower/settings.py and + new systems will require /etc/ansible-automation-platform/*.yaml + """ + if "production" not in settings.current_env.lower(): + return + + required_settings_paths = [ + os.path.dirname(settings_file_path), + "/etc/ansible-automation-platform/", + settings_dir, + ] + + for path in required_settings_paths: + if any([path in os.path.dirname(f) for f in settings._loaded_files]): + break + else: + from django.core.exceptions import ImproperlyConfigured # noqa + + msg = 'No AWX configuration found at %s.' % required_settings_paths + msg += '\nDefine the AWX_SETTINGS_FILE environment variable to ' + msg += 'specify an alternate path.' + raise ImproperlyConfigured(msg) diff --git a/awx/settings/kube_defaults.py b/awx/settings/kube_defaults.py new file mode 100644 index 000000000000..c30a7fe025fe --- /dev/null +++ b/awx/settings/kube_defaults.py @@ -0,0 +1,4 @@ +BROADCAST_WEBSOCKET_SECRET = '🤖starscream🤖' +BROADCAST_WEBSOCKET_PORT = 8052 +BROADCAST_WEBSOCKET_VERIFY_CERT = False +BROADCAST_WEBSOCKET_PROTOCOL = 'http' diff --git a/awx/settings/production.py b/awx/settings/production.py index e340de4fbbc1..bcf483b118cf 100644 --- a/awx/settings/production.py +++ b/awx/settings/production.py @@ -1,111 +1,13 @@ -# Copyright (c) 2015 Ansible, Inc. -# All Rights Reserved. - -# Production settings for AWX project. - -# Python +# This file exists for backwards compatibility only +# the current way of running AWX is to point settings to +# awx/settings/__init__.py as the entry point for the settings +# that is done by exporting: export DJANGO_SETTINGS_MODULE=awx.settings import os -import copy -import errno -import sys -import traceback - -# Django Split Settings -from split_settings.tools import optional, include - -# Load default settings. -from .defaults import * # NOQA - -DEBUG = False -TEMPLATE_DEBUG = DEBUG -SQL_DEBUG = DEBUG - -# Clear database settings to force production environment to define them. -DATABASES = {} - -# Clear the secret key to force production environment to define it. -SECRET_KEY = None - -# Hosts/domain names that are valid for this site; required if DEBUG is False -# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts -ALLOWED_HOSTS = [] - -# Ansible base virtualenv paths and enablement -# only used for deprecated fields and management commands for them -BASE_VENV_PATH = os.path.realpath("/var/lib/awx/venv") - -# Very important that this is editable (not read_only) in the API -AWX_ISOLATION_SHOW_PATHS = [ - '/etc/pki/ca-trust:/etc/pki/ca-trust:O', - '/usr/share/pki:/usr/share/pki:O', -] - -# Store a snapshot of default settings at this point before loading any -# customizable config files. -this_module = sys.modules[__name__] -local_vars = dir(this_module) -DEFAULTS_SNAPSHOT = {} # define after we save local_vars so we do not snapshot the snapshot -for setting in local_vars: - if setting.isupper(): - DEFAULTS_SNAPSHOT[setting] = copy.deepcopy(getattr(this_module, setting)) - -del local_vars # avoid temporary variables from showing up in dir(settings) -del this_module -# -############################################################################################### -# -# Any settings defined after this point will be marked as as a read_only database setting -# -################################################################################################ - -# Load settings from any .py files in the global conf.d directory specified in -# the environment, defaulting to /etc/tower/conf.d/. -settings_dir = os.environ.get('AWX_SETTINGS_DIR', '/etc/tower/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/tower/settings.py. -settings_file = os.environ.get('AWX_SETTINGS_FILE', '/etc/tower/settings.py') - -# Attempt to load settings from /etc/tower/settings.py first, followed by -# /etc/tower/conf.d/*.py. -try: - include(settings_file, optional(settings_files), scope=locals()) -except ImportError: - traceback.print_exc() - sys.exit(1) -except IOError: - from django.core.exceptions import ImproperlyConfigured - - included_file = locals().get('__included_file__', '') - if not included_file or included_file == settings_file: - # The import doesn't always give permission denied, so try to open the - # settings file directly. - try: - e = None - open(settings_file) - except IOError: - pass - if e and e.errno == errno.EACCES: - SECRET_KEY = 'permission-denied' - LOGGING = {} - else: - msg = 'No AWX configuration found at %s.' % settings_file - msg += '\nDefine the AWX_SETTINGS_FILE environment variable to ' - msg += 'specify an alternate path.' - raise ImproperlyConfigured(msg) - else: - raise - -# The below runs AFTER all of the custom settings are imported -# because conf.d files will define DATABASES and this should modify that -from .application_name import set_application_name -set_application_name(DATABASES, CLUSTER_HOST_ID) # NOQA +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings") +os.environ.setdefault("AWX_MODE", "production") -del set_application_name +from ansible_base.lib.dynamic_config import export +from . import DYNACONF # noqa -# Set the value of any feature flags that are defined in the local settings -for feature in list(FLAGS.keys()): # noqa: F405 - if feature in locals(): - FLAGS[feature][0]['value'] = locals()[feature] # noqa: F405 +export(__name__, DYNACONF) diff --git a/awx/settings/production_defaults.py b/awx/settings/production_defaults.py new file mode 100644 index 000000000000..5599b81753ef --- /dev/null +++ b/awx/settings/production_defaults.py @@ -0,0 +1,30 @@ +# Copyright (c) 2015 Ansible, Inc. +# All Rights Reserved. + +# Production settings for AWX project. + +import os + +DEBUG = False +TEMPLATE_DEBUG = DEBUG +SQL_DEBUG = DEBUG + +# Clear database settings to force production environment to define them. +DATABASES = {} + +# Clear the secret key to force production environment to define it. +SECRET_KEY = None + +# Hosts/domain names that are valid for this site; required if DEBUG is False +# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts +ALLOWED_HOSTS = [] + +# Ansible base virtualenv paths and enablement +# only used for deprecated fields and management commands for them +BASE_VENV_PATH = os.path.realpath("/var/lib/awx/venv") + +# Very important that this is editable (not read_only) in the API +AWX_ISOLATION_SHOW_PATHS = [ + '/etc/pki/ca-trust:/etc/pki/ca-trust:O', + '/usr/share/pki:/usr/share/pki:O', +] diff --git a/awx/settings/quiet_defaults.py b/awx/settings/quiet_defaults.py new file mode 100644 index 000000000000..1cb21720f7dd --- /dev/null +++ b/awx/settings/quiet_defaults.py @@ -0,0 +1,8 @@ +# Copyright (c) 2015 Ansible, Inc. +# All Rights Reserved. +# Development settings for AWX project, but with DEBUG disabled + +# Disable capturing DEBUG +DEBUG = False +TEMPLATE_DEBUG = DEBUG +SQL_DEBUG = DEBUG diff --git a/awx/urls.py b/awx/urls.py index daef360d5788..862dc5dcd9fd 100644 --- a/awx/urls.py +++ b/awx/urls.py @@ -37,7 +37,7 @@ def get_urlpatterns(prefix=None): re_path(r'^(?!api/).*', include('awx.ui.urls', namespace='ui')), ] - if settings.SETTINGS_MODULE == 'awx.settings.development': + if settings.DYNACONF.is_development_mode: try: import debug_toolbar diff --git a/licenses/django-split-settings.txt b/licenses/django-split-settings.txt deleted file mode 100644 index 3195eae727d0..000000000000 --- a/licenses/django-split-settings.txt +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013, 2General Oy -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of django-split-settings nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/dynaconf.txt b/licenses/dynaconf.txt new file mode 100644 index 000000000000..cbc6bbf2903b --- /dev/null +++ b/licenses/dynaconf.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Bruno Rocha + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/requirements/requirements.in b/requirements/requirements.in index b6ff41d14dda..45ad8dd530d7 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -22,9 +22,9 @@ django-guid django-oauth-toolkit<2.0.0 # Version 2.0.0 has breaking changes that will need to be worked out before upgrading django-polymorphic django-solo -django-split-settings djangorestframework>=3.15.0 djangorestframework-yaml +dynaconf<4 filelock GitPython>=3.1.37 # CVE-2023-41040 grpcio diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 34140e06dfef..941d251dbf36 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -158,10 +158,6 @@ django-polymorphic==3.1.0 # via -r /awx_devel/requirements/requirements.in django-solo==2.4.0 # via -r /awx_devel/requirements/requirements.in -django-split-settings==1.3.2 - # via - # -r /awx_devel/requirements/requirements.in - # django-ansible-base djangorestframework==3.15.2 # via # -r /awx_devel/requirements/requirements.in @@ -170,6 +166,8 @@ djangorestframework-yaml==2.0.0 # via -r /awx_devel/requirements/requirements.in durationpy==0.9 # via kubernetes +dynaconf==3.2.10 + # via -r /awx_devel/requirements/requirements.in enum-compat==0.0.3 # via asn1 filelock==3.16.1 diff --git a/requirements/requirements_git.txt b/requirements/requirements_git.txt index 99147161a2c1..557d5af87533 100644 --- a/requirements/requirements_git.txt +++ b/requirements/requirements_git.txt @@ -1,6 +1,7 @@ git+https://github.com/ansible/system-certifi.git@devel#egg=certifi # Remove pbr from requirements.in when moving ansible-runner to requirements.in git+https://github.com/ansible/ansible-runner.git@devel#egg=ansible-runner -django-ansible-base @ git+https://github.com/ansible/django-ansible-base@devel#egg=django-ansible-base[rest-filters,jwt_consumer,resource-registry,rbac,feature-flags] awx-plugins-core @ git+https://github.com/ansible/awx-plugins.git@devel#egg=awx-plugins-core[credentials-github-app] +# django-ansible-base @ git+https://github.com/ansible/django-ansible-base@devel#egg=django-ansible-base[rest-filters,jwt_consumer,resource-registry,rbac,feature-flags] +django-ansible-base @ git+https://github.com/rochacbruno/django-ansible-base@dynaconf_settings#egg=django-ansible-base[rest-filters,jwt_consumer,resource-registry,rbac,feature-flags] awx_plugins.interfaces @ git+https://github.com/ansible/awx_plugins.interfaces.git diff --git a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 index dd0b651f00b9..c224fdb940ee 100644 --- a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 +++ b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 @@ -42,6 +42,7 @@ services: DJANGO_SUPERUSER_PASSWORD: {{ admin_password }} UWSGI_MOUNT_PATH: {{ ingress_path }} DJANGO_COLORS: "${DJANGO_COLORS:-}" + DJANGO_SETTINGS_MODULE: "awx.settings" {% if loop.index == 1 %} RUN_MIGRATIONS: 1 {% endif %}