From 441067914b2d456314fb09670f6ed60cc26768bd Mon Sep 17 00:00:00 2001 From: Zack Kayyali Date: Thu, 12 Jun 2025 18:09:15 -0400 Subject: [PATCH] move to platform flags --- .../test_feature_flags_api.py | 42 ++++++++----------- awx/main/tests/functional/test_dispatch.py | 14 +++---- awx/main/tests/unit/test_tasks.py | 4 ++ awx/resource_api.py | 13 +++++- awx/settings/__init__.py | 8 ---- awx/settings/defaults.py | 7 +--- awx/settings/development_defaults.py | 12 +----- requirements/requirements_git.txt | 2 +- 8 files changed, 44 insertions(+), 58 deletions(-) diff --git a/awx/main/tests/functional/dab_feature_flags/test_feature_flags_api.py b/awx/main/tests/functional/dab_feature_flags/test_feature_flags_api.py index 490b3ac6e169..7601e4ddc4b6 100644 --- a/awx/main/tests/functional/dab_feature_flags/test_feature_flags_api.py +++ b/awx/main/tests/functional/dab_feature_flags/test_feature_flags_api.py @@ -1,36 +1,30 @@ import pytest -from django.test import override_settings - +from flags.state import get_flags, flag_state +from ansible_base.feature_flags.models import AAPFlag +from ansible_base.feature_flags.utils import create_initial_data as seed_feature_flags +from django.conf import settings from awx.main.models import User -@override_settings(FLAGS={}) @pytest.mark.django_db def test_feature_flags_list_endpoint(get): - bob = User.objects.create(username='bob', password='test_user', is_superuser=False) - - url = "/api/v2/feature_flags_state/" + bob = User.objects.create(username='bob', password='test_user', is_superuser=True) + url = "/api/v2/feature_flags/states/" response = get(url, user=bob, expect=200) - assert len(response.data) == 0 + assert len(get_flags()) > 0 + assert len(response.data["results"]) == len(get_flags()) -@override_settings( - FLAGS={ - "FEATURE_SOME_PLATFORM_FLAG_ENABLED": [ - {"condition": "boolean", "value": False}, - {"condition": "before date", "value": "2022-06-01T12:00Z"}, - ], - "FEATURE_SOME_PLATFORM_FLAG_FOO_ENABLED": [ - {"condition": "boolean", "value": True}, - ], - } -) @pytest.mark.django_db -def test_feature_flags_list_endpoint_override(get): - bob = User.objects.create(username='bob', password='test_user', is_superuser=False) +@pytest.mark.parametrize('flag_val', (True, False)) +def test_feature_flags_list_endpoint_override(get, flag_val): + bob = User.objects.create(username='bob', password='test_user', is_superuser=True) - url = "/api/v2/feature_flags_state/" + AAPFlag.objects.all().delete() + flag_name = "FEATURE_DISPATCHERD_ENABLED" + setattr(settings, flag_name, flag_val) + seed_feature_flags() + url = "/api/v2/feature_flags/states/" response = get(url, user=bob, expect=200) - assert len(response.data) == 2 - assert response.data["FEATURE_SOME_PLATFORM_FLAG_ENABLED"] is False - assert response.data["FEATURE_SOME_PLATFORM_FLAG_FOO_ENABLED"] is True + assert len(response.data["results"]) == 6 + assert flag_state(flag_name) == flag_val diff --git a/awx/main/tests/functional/test_dispatch.py b/awx/main/tests/functional/test_dispatch.py index a5a344d504fd..b752762fa6b9 100644 --- a/awx/main/tests/functional/test_dispatch.py +++ b/awx/main/tests/functional/test_dispatch.py @@ -5,11 +5,8 @@ import time import yaml from unittest import mock -from copy import deepcopy - +from flags.state import disable_flag, enable_flag from django.utils.timezone import now as tz_now -from django.conf import settings -from django.test.utils import override_settings import pytest from awx.main.models import Job, WorkflowJob, Instance @@ -302,13 +299,14 @@ def test_undefined_function_cannot_be_imported(self): assert str(result) == "No module named 'awx.foo'" # noqa +@pytest.mark.django_db class TestTaskPublisher: @pytest.fixture(autouse=True) def _disable_dispatcherd(self): - ffs = deepcopy(settings.FLAGS) - ffs['FEATURE_DISPATCHERD_ENABLED'][0]['value'] = False - with override_settings(FLAGS=ffs): - yield + flag_name = "FEATURE_DISPATCHERD_ENABLED" + disable_flag(flag_name) + yield + enable_flag(flag_name) def test_function_callable(self): assert add(2, 2) == 4 diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index 4ddbd5e5ce44..044e8bc8f769 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -461,6 +461,7 @@ def test_overwritten_jt_extra_vars(self, job, private_data_dir, mock_me): class TestGenericRun: + @pytest.mark.django_db(reset_sequences=True) def test_generic_failure(self, patch_Job, execution_environment, mock_me, mock_create_partition): job = Job(status='running', inventory=Inventory(), project=Project(local_path='/projects/_23_foo')) job.websocket_emit_status = mock.Mock() @@ -545,6 +546,7 @@ def test_survey_extra_vars(self, mock_me): private_data_dir, extra_vars, safe_dict = call_args assert extra_vars['super_secret'] == "CLASSIFIED" + @pytest.mark.django_db def test_awx_task_env(self, patch_Job, private_data_dir, execution_environment, mock_me): job = Job(project=Project(), inventory=Inventory()) job.execution_environment = execution_environment @@ -845,6 +847,7 @@ def test_multi_vault_password_ask(self, private_data_dir, job, mock_me): [None, '0'], ], ) + @pytest.mark.django_db def test_net_credentials(self, authorize, expected_authorize, job, private_data_dir, mock_me): task = jobs.RunJob() task.instance = job @@ -901,6 +904,7 @@ def test_multi_cloud(self, private_data_dir, mock_me): assert safe_env['AZURE_PASSWORD'] == HIDDEN_PASSWORD + @pytest.mark.django_db def test_awx_task_env(self, settings, private_data_dir, job, mock_me): settings.AWX_TASK_ENV = {'FOO': 'BAR'} task = jobs.RunJob() diff --git a/awx/resource_api.py b/awx/resource_api.py index 2009dfab8b78..cb28df51da16 100644 --- a/awx/resource_api.py +++ b/awx/resource_api.py @@ -1,6 +1,11 @@ from ansible_base.resource_registry.registry import ParentResource, ResourceConfig, ServiceAPIConfig, SharedResource -from ansible_base.resource_registry.shared_types import OrganizationType, TeamType, UserType - +from ansible_base.resource_registry.shared_types import ( + FeatureFlagType, + OrganizationType, + TeamType, + UserType, +) +from ansible_base.feature_flags.models import AAPFlag from awx.main import models @@ -19,4 +24,8 @@ class APIConfig(ServiceAPIConfig): shared_resource=SharedResource(serializer=TeamType, is_provider=False), parent_resources=[ParentResource(model=models.Organization, field_name="organization")], ), + ResourceConfig( + AAPFlag, + shared_resource=SharedResource(serializer=FeatureFlagType, is_provider=False), + ), ) diff --git a/awx/settings/__init__.py b/awx/settings/__init__.py index 641d442bbce5..2332863d7bb1 100644 --- a/awx/settings/__init__.py +++ b/awx/settings/__init__.py @@ -8,7 +8,6 @@ load_envvars, load_python_file_with_injected_context, load_standard_settings_files, - toggle_feature_flags, ) from .functions import ( assert_production_settings, @@ -71,12 +70,5 @@ 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/defaults.py b/awx/settings/defaults.py index 44ea309d10bb..3ff751f4c6e1 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -1106,11 +1106,8 @@ OPA_REQUEST_RETRIES = 2 # The number of retry attempts for connecting to the OPA server. Default is 2. # feature flags -FLAG_SOURCES = ('flags.sources.SettingsFlagsSource',) -FLAGS = { - 'FEATURE_INDIRECT_NODE_COUNTING_ENABLED': [{'condition': 'boolean', 'value': False}], - 'FEATURE_DISPATCHERD_ENABLED': [{'condition': 'boolean', 'value': False}], -} +FEATURE_INDIRECT_NODE_COUNTING_ENABLED = False +FEATURE_DISPATCHERD_ENABLED = False # Dispatcher worker lifetime. If set to None, workers will never be retired # based on age. Note workers will finish their last task before retiring if diff --git a/awx/settings/development_defaults.py b/awx/settings/development_defaults.py index 9725b0abb720..25b5d37b2c0d 100644 --- a/awx/settings/development_defaults.py +++ b/awx/settings/development_defaults.py @@ -11,8 +11,6 @@ # /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'] @@ -67,11 +65,5 @@ # 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}] - defaults_flags['FEATURE_DISPATCHERD_ENABLED'] = [{'condition': 'boolean', 'value': True}] - return {'FLAGS': defaults_flags} +FEATURE_INDIRECT_NODE_COUNTING_ENABLED = True +FEATURE_DISPATCHERD_ENABLED = True diff --git a/requirements/requirements_git.txt b/requirements/requirements_git.txt index 51a0f4f69708..b527b489989e 100644 --- a/requirements/requirements_git.txt +++ b/requirements/requirements_git.txt @@ -1,5 +1,5 @@ git+https://github.com/ansible/system-certifi.git@devel#egg=certifi git+https://github.com/ansible/ansible-runner.git@devel#egg=ansible-runner 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/zkayyali812/django-ansible-base@phase2/feature-flags/poc#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