Skip to content

Commit 0ffe04e

Browse files
rochacbrunoAlanCoding
authored andcommitted
feat: Manage Django Settings with Dynaconf
Dynaconf is being added from DAB factory to load Django Settings
1 parent ee739b5 commit 0ffe04e

23 files changed

+405
-297
lines changed

.coveragerc

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ exclude_also =
1919
branch = True
2020
omit =
2121
awx/main/migrations/*
22+
awx/settings/defaults.py
23+
awx/settings/*_defaults.py
2224
source =
2325
.
2426
source_pkgs =

awx/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ def version_file():
6262

6363
def prepare_env():
6464
# Update the default settings environment variable based on current mode.
65-
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'awx.settings.%s' % MODE)
65+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'awx.settings')
66+
os.environ.setdefault('AWX_MODE', MODE)
6667
# Hide DeprecationWarnings when running in production. Need to first load
6768
# settings to apply our filter after Django's own warnings filter.
6869
from django.conf import settings

awx/api/generics.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def get_view_description(view, html=False):
161161

162162

163163
def get_default_schema():
164-
if settings.SETTINGS_MODULE == 'awx.settings.development':
164+
if settings.DYNACONF.is_development_mode:
165165
from awx.api.swagger import schema_view
166166

167167
return schema_view

awx/main/tests/unit/test_settings.py

+44-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
from split_settings.tools import include
2-
3-
41
LOCAL_SETTINGS = (
52
'ALLOWED_HOSTS',
63
'BROADCAST_WEBSOCKET_PORT',
@@ -16,13 +13,14 @@
1613

1714

1815
def test_postprocess_auth_basic_enabled():
19-
locals().update({'__file__': __file__})
16+
"""The final loaded settings should have basic auth enabled."""
17+
from awx.settings import REST_FRAMEWORK
2018

21-
include('../../../settings/defaults.py', scope=locals())
22-
assert 'awx.api.authentication.LoggedBasicAuthentication' in locals()['REST_FRAMEWORK']['DEFAULT_AUTHENTICATION_CLASSES']
19+
assert 'awx.api.authentication.LoggedBasicAuthentication' in REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES']
2320

2421

2522
def test_default_settings():
23+
"""Ensure that all default settings are present in the snapshot."""
2624
from django.conf import settings
2725

2826
for k in dir(settings):
@@ -31,3 +29,43 @@ def test_default_settings():
3129
default_val = getattr(settings.default_settings, k, None)
3230
snapshot_val = settings.DEFAULTS_SNAPSHOT[k]
3331
assert default_val == snapshot_val, f'Setting for {k} does not match shapshot:\nsnapshot: {snapshot_val}\ndefault: {default_val}'
32+
33+
34+
def test_django_conf_settings_is_awx_settings():
35+
"""Ensure that the settings loaded from dynaconf are the same as the settings delivered to django."""
36+
from django.conf import settings
37+
from awx.settings import REST_FRAMEWORK
38+
39+
assert settings.REST_FRAMEWORK == REST_FRAMEWORK
40+
41+
42+
def test_dynaconf_is_awx_settings():
43+
"""Ensure that the settings loaded from dynaconf are the same as the settings delivered to django."""
44+
from django.conf import settings
45+
from awx.settings import REST_FRAMEWORK
46+
47+
assert settings.DYNACONF.REST_FRAMEWORK == REST_FRAMEWORK
48+
49+
50+
def test_development_settings_can_be_directly_imported(monkeypatch):
51+
"""Ensure that the development settings can be directly imported."""
52+
monkeypatch.setenv('AWX_MODE', 'development')
53+
from django.conf import settings
54+
from awx.settings.development import REST_FRAMEWORK
55+
from awx.settings.development import DEBUG # actually set on defaults.py and not overridden in development.py
56+
57+
assert settings.REST_FRAMEWORK == REST_FRAMEWORK
58+
assert DEBUG is True
59+
60+
61+
def test_merge_application_name():
62+
"""Ensure that the merge_application_name function works as expected."""
63+
from awx.settings.functions import merge_application_name
64+
65+
settings = {
66+
"DATABASES__default__ENGINE": "django.db.backends.postgresql",
67+
"CLUSTER_HOST_ID": "test-cluster-host-id",
68+
}
69+
result = merge_application_name(settings)["DATABASES__default__OPTIONS__application_name"]
70+
assert result.startswith("awx-")
71+
assert "test-cluster" in result

awx/settings/__init__.py

+80
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,82 @@
11
# Copyright (c) 2015 Ansible, Inc.
22
# All Rights Reserved.
3+
import os
4+
import copy
5+
from ansible_base.lib.dynamic_config import (
6+
factory,
7+
export,
8+
load_envvars,
9+
load_python_file_with_injected_context,
10+
load_standard_settings_files,
11+
toggle_feature_flags,
12+
)
13+
from .functions import (
14+
assert_production_settings,
15+
merge_application_name,
16+
add_backwards_compatibility,
17+
load_extra_development_files,
18+
)
19+
20+
add_backwards_compatibility()
21+
22+
# Create a the standard DYNACONF instance which will come with DAB defaults
23+
# This loads defaults.py and environment specific file e.g: development_defaults.py
24+
DYNACONF = factory(
25+
__name__,
26+
"AWX",
27+
environments=("development", "production", "quiet", "kube"),
28+
settings_files=["defaults.py"],
29+
)
30+
31+
# Store snapshot before loading any custom config file
32+
DYNACONF.set(
33+
"DEFAULTS_SNAPSHOT",
34+
copy.deepcopy(DYNACONF.as_dict(internal=False)),
35+
loader_identifier="awx.settings:DEFAULTS_SNAPSHOT",
36+
)
37+
38+
#############################################################################################
39+
# Settings loaded before this point will be allowed to be overridden by the database settings
40+
# Any settings loaded after this point will be marked as as a read_only database setting
41+
#############################################################################################
42+
43+
# Load extra settings files from the following directories
44+
# /etc/tower/conf.d/ and /etc/tower/
45+
# this is the legacy location, kept for backwards compatibility
46+
settings_dir = os.environ.get('AWX_SETTINGS_DIR', '/etc/tower/conf.d/')
47+
settings_files_path = os.path.join(settings_dir, '*.py')
48+
settings_file_path = os.environ.get('AWX_SETTINGS_FILE', '/etc/tower/settings.py')
49+
load_python_file_with_injected_context(settings_files_path, settings=DYNACONF)
50+
load_python_file_with_injected_context(settings_file_path, settings=DYNACONF)
51+
52+
# Load extra settings files from the following directories
53+
# /etc/ansible-automation-platform/{settings,flags,.secrets}.yaml
54+
# and /etc/ansible-automation-platform/awx/{settings,flags,.secrets}.yaml
55+
# this is the new standard location for all services
56+
load_standard_settings_files(DYNACONF)
57+
58+
# Load optional development only settings files
59+
load_extra_development_files(DYNACONF)
60+
61+
# Check at least one setting file has been loaded in production mode
62+
assert_production_settings(DYNACONF, settings_dir, settings_file_path)
63+
64+
# Load envvars at the end to allow them to override everything loaded so far
65+
load_envvars(DYNACONF)
66+
67+
# This must run after all custom settings are loaded
68+
DYNACONF.update(
69+
merge_application_name(DYNACONF),
70+
loader_identifier="awx.settings:merge_application_name",
71+
merge=True,
72+
)
73+
74+
# Toggle feature flags based on installer settings
75+
DYNACONF.update(
76+
toggle_feature_flags(DYNACONF),
77+
loader_identifier="awx.settings:toggle_feature_flags",
78+
merge=True,
79+
)
80+
81+
# Update django.conf.settings with DYNACONF values
82+
export(__name__, DYNACONF)

awx/settings/application_name.py

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def get_application_name(CLUSTER_HOST_ID, function=''):
2525

2626

2727
def set_application_name(DATABASES, CLUSTER_HOST_ID, function=''):
28+
"""In place modification of DATABASES to set the application name for the connection."""
2829
# If settings files were not properly passed DATABASES could be {} at which point we don't need to set the app name.
2930
if not DATABASES or 'default' not in DATABASES:
3031
return

awx/settings/defaults.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99
import socket
1010
from datetime import timedelta
1111

12-
from split_settings.tools import include
13-
14-
1512
DEBUG = True
1613
SQL_DEBUG = DEBUG
1714

@@ -1015,16 +1012,15 @@
10151012
}
10161013
}
10171014

1018-
10191015
# django-ansible-base
10201016
ANSIBLE_BASE_TEAM_MODEL = 'main.Team'
10211017
ANSIBLE_BASE_ORGANIZATION_MODEL = 'main.Organization'
10221018
ANSIBLE_BASE_RESOURCE_CONFIG_MODULE = 'awx.resource_api'
10231019
ANSIBLE_BASE_PERMISSION_MODEL = 'main.Permission'
10241020

1025-
from ansible_base.lib import dynamic_config # noqa: E402
1026-
1027-
include(os.path.join(os.path.dirname(dynamic_config.__file__), 'dynamic_settings.py'))
1021+
# Defaults to be overridden by DAB
1022+
SPECTACULAR_SETTINGS = {}
1023+
OAUTH2_PROVIDER = {}
10281024

10291025
# Add a postfix to the API URL patterns
10301026
# example if set to '' API pattern will be /api

awx/settings/development.py

+9-125
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,13 @@
1-
# Copyright (c) 2015 Ansible, Inc.
2-
# All Rights Reserved.
3-
4-
# Development settings for AWX project.
5-
6-
# Python
1+
# This file exists for backwards compatibility only
2+
# the current way of running AWX is to point settings to
3+
# awx/settings/__init__.py as the entry point for the settings
4+
# that is done by exporting: export DJANGO_SETTINGS_MODULE=awx.settings
75
import os
8-
import socket
9-
import copy
10-
import sys
11-
import traceback
12-
13-
# Centos-7 doesn't include the svg mime type
14-
# /usr/lib64/python/mimetypes.py
15-
import mimetypes
16-
17-
# Django Split Settings
18-
from split_settings.tools import optional, include
19-
20-
# Load default settings.
21-
from .defaults import * # NOQA
22-
23-
# awx-manage shell_plus --notebook
24-
NOTEBOOK_ARGUMENTS = ['--NotebookApp.token=', '--ip', '0.0.0.0', '--port', '9888', '--allow-root', '--no-browser']
25-
26-
# print SQL queries in shell_plus
27-
SHELL_PLUS_PRINT_SQL = False
28-
29-
# show colored logs in the dev environment
30-
# to disable this, set `COLOR_LOGS = False` in awx/settings/local_settings.py
31-
COLOR_LOGS = True
32-
LOGGING['handlers']['console']['()'] = 'awx.main.utils.handlers.ColorHandler' # noqa
33-
34-
ALLOWED_HOSTS = ['*']
35-
36-
mimetypes.add_type("image/svg+xml", ".svg", True)
37-
mimetypes.add_type("image/svg+xml", ".svgz", True)
38-
39-
# Disallow sending session cookies over insecure connections
40-
SESSION_COOKIE_SECURE = False
41-
42-
# Disallow sending csrf cookies over insecure connections
43-
CSRF_COOKIE_SECURE = False
44-
45-
# Disable Pendo on the UI for development/test.
46-
# Note: This setting may be overridden by database settings.
47-
PENDO_TRACKING_STATE = "off"
48-
INSIGHTS_TRACKING_STATE = False
49-
50-
# debug toolbar and swagger assume that requirements/requirements_dev.txt are installed
51-
52-
INSTALLED_APPS += ['drf_yasg', 'debug_toolbar'] # NOQA
53-
54-
MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware'] + MIDDLEWARE # NOQA
55-
56-
DEBUG_TOOLBAR_CONFIG = {'ENABLE_STACKTRACES': True}
57-
58-
# Configure a default UUID for development only.
59-
SYSTEM_UUID = '00000000-0000-0000-0000-000000000000'
60-
INSTALL_UUID = '00000000-0000-0000-0000-000000000000'
61-
62-
# Ansible base virtualenv paths and enablement
63-
# only used for deprecated fields and management commands for them
64-
BASE_VENV_PATH = os.path.realpath("/var/lib/awx/venv")
65-
66-
CLUSTER_HOST_ID = socket.gethostname()
67-
68-
AWX_CALLBACK_PROFILE = True
69-
70-
# this modifies FLAGS set by defaults
71-
FLAGS['FEATURE_INDIRECT_NODE_COUNTING_ENABLED'] = [{'condition': 'boolean', 'value': True}] # noqa
72-
73-
# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!=================================
74-
# Disable normal scheduled/triggered task managers (DependencyManager, TaskManager, WorkflowManager).
75-
# Allows user to trigger task managers directly for debugging and profiling purposes.
76-
# Only works in combination with settings.SETTINGS_MODULE == 'awx.settings.development'
77-
AWX_DISABLE_TASK_MANAGERS = False
78-
79-
# Needed for launching runserver in debug mode
80-
# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!=================================
81-
82-
# Store a snapshot of default settings at this point before loading any
83-
# customizable config files.
84-
this_module = sys.modules[__name__]
85-
local_vars = dir(this_module)
86-
DEFAULTS_SNAPSHOT = {} # define after we save local_vars so we do not snapshot the snapshot
87-
for setting in local_vars:
88-
if setting.isupper():
89-
DEFAULTS_SNAPSHOT[setting] = copy.deepcopy(getattr(this_module, setting))
90-
91-
del local_vars # avoid temporary variables from showing up in dir(settings)
92-
del this_module
93-
#
94-
###############################################################################################
95-
#
96-
# Any settings defined after this point will be marked as as a read_only database setting
97-
#
98-
################################################################################################
99-
100-
# If there is an `/etc/tower/settings.py`, include it.
101-
# If there is a `/etc/tower/conf.d/*.py`, include them.
102-
include(optional('/etc/tower/settings.py'), scope=locals())
103-
include(optional('/etc/tower/conf.d/*.py'), scope=locals())
104-
105-
# If any local_*.py files are present in awx/settings/, use them to override
106-
# default settings for development. If not present, we can still run using
107-
# only the defaults.
108-
# this needs to stay at the bottom of this file
109-
try:
110-
if os.getenv('AWX_KUBE_DEVEL', False):
111-
include(optional('development_kube.py'), scope=locals())
112-
else:
113-
include(optional('local_*.py'), scope=locals())
114-
except ImportError:
115-
traceback.print_exc()
116-
sys.exit(1)
117-
118-
# The below runs AFTER all of the custom settings are imported
119-
# because conf.d files will define DATABASES and this should modify that
120-
from .application_name import set_application_name
1216

122-
set_application_name(DATABASES, CLUSTER_HOST_ID) # NOQA
7+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings")
8+
os.environ.setdefault("AWX_MODE", "development")
1239

124-
del set_application_name
10+
from ansible_base.lib.dynamic_config import export
11+
from . import DYNACONF # noqa
12512

126-
# Set the value of any feature flags that are defined in the local settings
127-
for feature in list(FLAGS.keys()): # noqa: F405
128-
if feature in locals():
129-
FLAGS[feature][0]['value'] = locals()[feature] # noqa: F405
13+
export(__name__, DYNACONF)

0 commit comments

Comments
 (0)