Skip to content

Commit

Permalink
[AAP-39137] - Add DAB feature-flags endpoint (#2411)
Browse files Browse the repository at this point in the history
* Add DAB feature-flags endpoint

---------

Co-authored-by: Bruno Rocha <[email protected]>
  • Loading branch information
zkayyali812 and rochacbruno committed Feb 5, 2025
1 parent e6ed459 commit b525b5f
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 17 deletions.
35 changes: 34 additions & 1 deletion galaxy_ng/app/dynaconf_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from django.apps import apps
from django_auth_ldap.config import LDAPSearch
from dynaconf import Dynaconf, Validator
from dynaconf.utils.functional import empty

from galaxy_ng.app.dynamic_settings import DYNAMIC_SETTINGS_SCHEMA

Expand Down Expand Up @@ -70,6 +71,7 @@ def post(settings: Dynaconf, run_dynamic: bool = True, run_validate: bool = True
data.update(configure_api_base_path(settings))
data.update(configure_legacy_roles(settings))
data.update(configure_dab_required_settings(settings))
data.update(toggle_feature_flags(settings))

# These should go last, and it needs to receive the data from the previous configuration
# functions because this function configures the rest framework auth classes based off
Expand Down Expand Up @@ -795,10 +797,41 @@ def alter_hostname_settings(

def configure_dab_required_settings(settings: Dynaconf) -> dict[str, Any]:
dab_settings = get_dab_settings(
installed_apps=[*settings.INSTALLED_APPS, 'ansible_base.jwt_consumer'],
installed_apps=[
*settings.INSTALLED_APPS,
# jwt_consumer will not be part of the final INSTALLED_APPS
# but passed here to get the required jwt settings from DAB.
'ansible_base.jwt_consumer',
],
rest_framework=settings.REST_FRAMEWORK,
spectacular_settings=settings.SPECTACULAR_SETTINGS,
authentication_backends=settings.AUTHENTICATION_BACKENDS,
middleware=settings.MIDDLEWARE,
templates=settings.TEMPLATES
)
# This doesn't perform any merging, it sets only keys that are not already set
# NOTE: this needs refactoring when integrating with new Dynaconf factory from DAB
return {k: v for k, v in dab_settings.items() if k not in settings}


def toggle_feature_flags(settings: Dynaconf) -> dict[str, Any]:
"""Toggle FLAGS based on installer settings.
FLAGS is a django-flags formatted dictionary.
FLAGS={
"FEATURE_SOME_PLATFORM_FLAG_ENABLED": [
{"condition": "boolean", "value": False, "required": True},
{"condition": "before date", "value": "2022-06-01T12:00Z"},
]
}
Installers will place `FEATURE_SOME_PLATFORM_FLAG_ENABLED=True/False` in the settings file.
This function will update the value in the index 0 in FLAGS with the installer value.
"""
data = {}
for feature_name, feature_content in settings.get("FLAGS", {}).items():
if (installer_value := settings.get(feature_name, empty)) is not empty:
feature_content[0]["value"] = installer_value
data[f"FLAGS__{feature_name}"] = feature_content
return data
3 changes: 2 additions & 1 deletion galaxy_ng/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@
'crum',
'ansible_base.resource_registry',
'ansible_base.rbac',
'flags',
'social_django',
'flags',
'ansible_base.feature_flags',
'dynaconf_merge_unique',
]

Expand Down
2 changes: 2 additions & 0 deletions galaxy_ng/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from ansible_base.resource_registry.urls import (
urlpatterns as resource_api_urls,
)
from ansible_base.feature_flags.urls import api_version_urls as feature_flags_urls

API_PATH_PREFIX = settings.GALAXY_API_PATH_PREFIX.strip("/")

Expand Down Expand Up @@ -52,6 +53,7 @@
]

urlpatterns.append(path(f"{API_PATH_PREFIX}/", include(resource_api_urls)))
urlpatterns.append(path(f"{API_PATH_PREFIX}/", include(feature_flags_urls)))
# urlpatterns.append(path(f"{API_PATH_PREFIX}/", include(dab_rbac_urls)))

if settings.get("API_ROOT") != "/pulp/":
Expand Down
19 changes: 19 additions & 0 deletions galaxy_ng/tests/integration/dab/test_feature_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import os
import pytest


@pytest.mark.deployment_standalone
@pytest.mark.min_hub_version("4.10")
@pytest.mark.skipif(
os.getenv("ENABLE_DAB_TESTS"),
reason="Skipping test because ENABLE_DAB_TESTS is set"
)
def test_feature_flags_endpoint_is_exposed(galaxy_client):
"""
We want the download url to point at the gateway
"""
gc = galaxy_client("admin")
flag_url = "feature_flags_state/"
resp = gc.get(flag_url, parse_json=False)
assert resp.status_code == 200
assert resp.headers.get('Content-Type') == 'application/json'
42 changes: 42 additions & 0 deletions galaxy_ng/tests/unit/api/test_api_dab_feature_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# from django.conf import settings

# from .base import BaseTestCase, get_current_ui_url
# from django.test import override_settings


# class TestUiFeatureFlagsView(BaseTestCase):
# def setUp(self):
# super().setUp()
# self.original_setting = settings.GALAXY_FEATURE_FLAGS
# settings.GALAXY_FEATURE_FLAGS = {
# 'execution_environments': True,
# 'widget_x': False,
# }

# def tearDown(self):
# super().tearDown()
# settings.GALAXY_FEATURE_FLAGS = self.original_setting

# def test_feature_flags_dab_api(self):
# response = self.client.get("/feature_flags_definition/")
# assert response.status_code == 200, response.data
# # Test number of feature flags.
# # Modify each time a flag is added to default settings
# assert len(response.data) == 0

# @override_settings(
# FLAGS={
# "FEATURE_SOME_PLATFORM_FLAG_ENABLED": [
# {"condition": "boolean", "value": False, "required": True},
# {"condition": "before date", "value": "2022-06-01T12:00Z"},
# ]
# }
# )
# @pytest.mark.django_db
# def test_feature_flags_override_flags(admin_client):
# response = admin_client.get(f"{api_url_v1}/feature_flags_definition/")
# assert response.status_code == status.HTTP_200_OK, response.data
# assert len(response.data) == 1 # Validates number of feature flags
# assert (
# len(response.data["FEATURE_SOME_PLATFORM_FLAG_ENABLED"]) == 2
# ) # Validates number of conditions
30 changes: 30 additions & 0 deletions galaxy_ng/tests/unit/app/test_dynaconf_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def validate(*args, **kwargs):
"AUTHENTICATION_BACKEND_PRESETS_DATA": copy.deepcopy(AUTHENTICATION_BACKEND_PRESETS_DATA),
"BASE_DIR": "templates",
"validators": SuperValidator(),
"TEMPLATES": [],
"FLAGS": {},
}


Expand Down Expand Up @@ -338,3 +340,31 @@ def test_dynaconf_hooks_authentication_backends_and_classes(
print(e)
"""
assert new_settings.get(key) == val


def test_dynaconf_hooks_toggle_feature_flags():
"""
FLAGS is a django-flags formatted dictionary.
Installers will place `FEATURE_****=True/False` in the settings file.
This function will update the value in the index 0 in FLAGS with the installer value.
"""
xsettings = SuperDict()
xsettings.update(copy.deepcopy(BASE_SETTINGS))

# Start with a feature flag that is disabled `value: False`
xsettings["FLAGS"] = {
"FEATURE_SOME_PLATFORM_FLAG_ENABLED": [
{"condition": "boolean", "value": False, "required": True},
{"condition": "before date", "value": "2022-06-01T12:00Z"},
]
}

# assume installer has enabled the feature flag on settings file
xsettings["FEATURE_SOME_PLATFORM_FLAG_ENABLED"] = True

# Run the post hook
new_settings = post_hook(xsettings, run_dynamic=True, run_validate=True)

# Check that the feature flag under FLAGS is now enabled
# the hook will return Dynaconf merging syntax.
assert new_settings["FLAGS__FEATURE_SOME_PLATFORM_FLAG_ENABLED"][0]["value"] is True
2 changes: 1 addition & 1 deletion profiles/base/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM localhost/oci_env/pulp:base

# Define the build argument
ARG DJANGO_ANSIBLE_BASE_BRANCH=2024.12.13
ARG DJANGO_ANSIBLE_BASE_BRANCH=2025.1.31

# Set the environment variable based on the build argument
ENV DJANGO_ANSIBLE_BASE_BRANCH=${DJANGO_ANSIBLE_BASE_BRANCH}
Expand Down
6 changes: 3 additions & 3 deletions profiles/dab/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ services:
_galaxy_base:
build:
args:
DJANGO_ANSIBLE_BASE_BRANCH: "2024.12.13"
DJANGO_ANSIBLE_BASE_BRANCH: "2025.1.31"
environment:
DJANGO_ANSIBLE_BASE_BRANCH: "2024.12.13"
DJANGO_ANSIBLE_BASE_BRANCH: "2025.1.31"

pulp:
environment:
PULP_WORKERS: "1"
DJANGO_ANSIBLE_BASE_BRANCH: "2024.12.13"
DJANGO_ANSIBLE_BASE_BRANCH: "2025.1.31"

volumes:
pulp_certs:
6 changes: 3 additions & 3 deletions profiles/dab_jwt/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ services:
_galaxy_base:
build:
args:
DJANGO_ANSIBLE_BASE_BRANCH: "2024.12.13"
DJANGO_ANSIBLE_BASE_BRANCH: "2025.1.31"
environment:
DJANGO_ANSIBLE_BASE_BRANCH: "2024.12.13"
DJANGO_ANSIBLE_BASE_BRANCH: "2025.1.31"

pulp:
environment:
PULP_WORKERS: "1"
DJANGO_ANSIBLE_BASE_BRANCH: "2024.12.13"
DJANGO_ANSIBLE_BASE_BRANCH: "2025.1.31"

jwtproxy:
build:
Expand Down
6 changes: 4 additions & 2 deletions requirements/requirements.common.txt
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ django==4.2.17
# insights-analytics-collector
# pulpcore
# social-auth-app-django
django-ansible-base[jwt-consumer] @ git+https://github.com/ansible/django-ansible-base@2024.12.13
django-ansible-base[feature-flags,jwt-consumer] @ git+https://github.com/ansible/django-ansible-base@2025.1.31
# via galaxy-ng (setup.py)
django-auth-ldap==4.0.0
# via galaxy-ng (setup.py)
Expand All @@ -117,7 +117,9 @@ django-crum==0.7.9
django-filter==23.5
# via pulpcore
django-flags==5.0.13
# via galaxy-ng (setup.py)
# via
# django-ansible-base
# galaxy-ng (setup.py)
django-guid==3.4.0
# via pulpcore
django-import-export==3.3.9
Expand Down
6 changes: 4 additions & 2 deletions requirements/requirements.insights.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ django==4.2.17
# insights-analytics-collector
# pulpcore
# social-auth-app-django
django-ansible-base[jwt-consumer] @ git+https://github.com/ansible/django-ansible-base@2024.12.13
django-ansible-base[feature-flags,jwt-consumer] @ git+https://github.com/ansible/django-ansible-base@2025.1.31
# via galaxy-ng (setup.py)
django-auth-ldap==4.0.0
# via galaxy-ng (setup.py)
Expand All @@ -131,7 +131,9 @@ django-crum==0.7.9
django-filter==23.5
# via pulpcore
django-flags==5.0.13
# via galaxy-ng (setup.py)
# via
# django-ansible-base
# galaxy-ng (setup.py)
django-guid==3.4.0
# via pulpcore
django-import-export==3.3.9
Expand Down
6 changes: 4 additions & 2 deletions requirements/requirements.standalone.txt
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ django==4.2.17
# insights-analytics-collector
# pulpcore
# social-auth-app-django
django-ansible-base[jwt-consumer] @ git+https://github.com/ansible/django-ansible-base@2024.12.13
django-ansible-base[feature-flags,jwt-consumer] @ git+https://github.com/ansible/django-ansible-base@2025.1.31
# via galaxy-ng (setup.py)
django-auth-ldap==4.0.0
# via galaxy-ng (setup.py)
Expand All @@ -117,7 +117,9 @@ django-crum==0.7.9
django-filter==23.5
# via pulpcore
django-flags==5.0.13
# via galaxy-ng (setup.py)
# via
# django-ansible-base
# galaxy-ng (setup.py)
django-guid==3.4.0
# via pulpcore
django-import-export==3.3.9
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ def run(self):
return super().run()


django_ansible_base_branch = os.getenv('DJANGO_ANSIBLE_BASE_BRANCH', '2024.12.13')
django_ansible_base_branch = os.getenv('DJANGO_ANSIBLE_BASE_BRANCH', '2025.1.31')
django_ansible_base_dependency = (
'django-ansible-base[jwt-consumer] @ '
'django-ansible-base[jwt-consumer,feature-flags] @ '
f'git+https://github.com/ansible/django-ansible-base@{django_ansible_base_branch}'
)

Expand Down

0 comments on commit b525b5f

Please sign in to comment.