Skip to content

Commit af1e97e

Browse files
committed
attempt moving apis to gw
1 parent e483806 commit af1e97e

File tree

15 files changed

+49
-175
lines changed

15 files changed

+49
-175
lines changed

ansible_base/feature_flags/migrations/0001_initial.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2.17 on 2025-03-21 13:04
1+
# Generated by Django 4.2.17 on 2025-03-26 17:52
22

33
import ansible_base.feature_flags.models.aap_flag
44
from django.conf import settings
@@ -29,7 +29,6 @@ class Migration(migrations.Migration):
2929
('visibility', models.CharField(choices=[('public', 'public'), ('private', 'private')], help_text='The visibility level of the feature flag. If private, flag is hidden.', max_length=20)),
3030
('toggle_type', models.CharField(choices=[('install-time', 'install-time'), ('run-time', 'run-time')], default='run-time', help_text="Details whether a flag is toggle-able at run-time or install-time. (Default: 'run-time').", max_length=20)),
3131
('description', models.CharField(default='', help_text='A detailed description giving an overview of the feature flag.', max_length=300)),
32-
('version_added', models.CharField(help_text='The Ansible Automation Platform version the feature flag was added in.', max_length=30)),
3332
('labels', models.JSONField(blank=True, default=list, help_text='A list of labels for the feature flag.', null=True)),
3433
('created_by', models.ForeignKey(default=None, editable=False, help_text='The user who created this resource.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_created+', to=settings.AUTH_USER_MODEL)),
3534
('modified_by', models.ForeignKey(default=None, editable=False, help_text='The user who last modified this resource.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_modified+', to=settings.AUTH_USER_MODEL)),

ansible_base/feature_flags/models/aap_flag.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
from django.db import models
33
from django.utils.translation import gettext_lazy as _
44

5+
from ansible_base.activitystream.models import AuditableModel
56
from ansible_base.lib.abstract_models.common import NamedCommonModel
7+
from ansible_base.resource_registry.fields import AnsibleResourceField
68

79

810
def validate_feature_flag_name(value: str):
911
if not value.startswith('FEATURE_') or not value.endswith('_ENABLED'):
1012
raise ValidationError(_("Feature flag names must follow the format of `FEATURE_<flag-name>_ENABLED`"))
1113

1214

13-
class AAPFlag(NamedCommonModel):
15+
class AAPFlag(NamedCommonModel, AuditableModel):
1416
class Meta:
1517
app_label = "dab_feature_flags"
1618
unique_together = ("name", "condition", "value")
@@ -23,6 +25,8 @@ def __str__(self):
2325
required=" (required)" if self.required else "",
2426
)
2527

28+
resource = AnsibleResourceField(primary_key_field="id")
29+
2630
name = models.CharField(
2731
max_length=64,
2832
null=False,
@@ -58,7 +62,4 @@ def __str__(self):
5862
help_text=_("Details whether a flag is toggle-able at run-time or install-time. (Default: 'run-time')."),
5963
)
6064
description = models.CharField(max_length=300, null=False, default="", help_text=_("A detailed description giving an overview of the feature flag."))
61-
version_added = models.CharField(
62-
max_length=30, null=False, help_text=_("The Ansible Automation Platform version the feature flag was added in."), blank=False
63-
)
6465
labels = models.JSONField(null=True, default=list, help_text=_("A list of labels for the feature flag."), blank=True)
Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,17 @@
11
from flags.state import flag_state
2-
3-
from ansible_base.feature_flags.models import AAPFlag
4-
from ansible_base.lib.serializers.common import NamedCommonModelSerializer
2+
from rest_framework import serializers
53

64
from .utils import get_django_flags
75

86

9-
class FeatureFlagSerializer(NamedCommonModelSerializer):
10-
"""Serialize list of feature flags"""
11-
12-
class Meta:
13-
model = AAPFlag
14-
fields = NamedCommonModelSerializer.Meta.fields + [x.name for x in AAPFlag._meta.concrete_fields]
15-
read_only_fields = ["name", "condition", "required", "support_level", "visibility", "toggle_type", "description", "version_added", "labels"]
16-
17-
def to_representation(self, instance):
18-
ret = super().to_representation(instance)
19-
return ret
20-
21-
22-
# TODO: Remove once all components are migrated to the new endpont.
23-
class OldFeatureFlagSerializer(NamedCommonModelSerializer):
7+
# TODO: This view and its serializer can be removed after functionality is migrated over to new class
8+
class FeatureFlagSerializer(serializers.Serializer):
249
"""Serialize list of feature flags"""
2510

26-
class Meta:
27-
model = AAPFlag
28-
fields = NamedCommonModelSerializer.Meta.fields + [x.name for x in AAPFlag._meta.concrete_fields]
29-
read_only_fields = ["name", "condition", "required", "support_level", "visibility", "toggle_type", "description", "version_added", "labels"]
30-
3111
def to_representation(self) -> dict:
3212
return_data = {}
3313
feature_flags = get_django_flags()
3414
for feature_flag in feature_flags:
3515
return_data[feature_flag] = flag_state(feature_flag)
16+
3617
return return_data

ansible_base/feature_flags/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ def update_feature_flag(existing: AAPFlag, new):
2828

2929
existing.support_level = new['support_level']
3030
existing.visibility = new['visibility']
31-
existing.version_added = new['version_added']
3231
if 'required' in new:
3332
existing.required = new['required']
3433
if 'toggle_type' in new:

ansible_base/feature_flags/views.py

Lines changed: 5 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,25 @@
1-
from django.shortcuts import get_object_or_404
21
from django.utils.translation import gettext_lazy as _
3-
from flags.state import flag_enabled, flag_state, get_flags
4-
from rest_framework import status
52
from rest_framework.response import Response
6-
from rest_framework.viewsets import ModelViewSet
73

8-
from ansible_base.feature_flags.models import AAPFlag
9-
from ansible_base.feature_flags.serializers import FeatureFlagSerializer, OldFeatureFlagSerializer
4+
from ansible_base.feature_flags.serializers import FeatureFlagSerializer
105
from ansible_base.lib.utils.views.ansible_base import AnsibleBaseView
11-
from ansible_base.lib.utils.views.django_app_api import AnsibleBaseDjangoAppApiView
12-
from ansible_base.lib.utils.views.permissions import IsSuperuserOrAuditor
13-
from ansible_base.rest_pagination import DefaultPaginator
146

15-
from .utils import get_django_flags, is_boolean_str
7+
from .utils import get_django_flags
168

179

18-
class FeatureFlagsStatesView(AnsibleBaseDjangoAppApiView, ModelViewSet):
19-
"""
20-
A view class for displaying feature flags states
21-
"""
22-
23-
queryset = AAPFlag.objects.order_by('id')
24-
permission_classes = [IsSuperuserOrAuditor]
25-
http_method_names = ['get', 'head', 'options']
26-
27-
def list(self, request):
28-
paginator = DefaultPaginator()
29-
flags = get_flags()
30-
ret = []
31-
for flag in flags:
32-
ret.append({"flag_name": flag, "flag_state": flag_state(flag)})
33-
result_page = paginator.paginate_queryset(ret, request)
34-
return paginator.get_paginated_response(result_page)
35-
36-
37-
class FeatureFlagsView(AnsibleBaseDjangoAppApiView, ModelViewSet):
10+
# TODO: This view and its serializer can be removed after functionality is migrated over to new class
11+
class FeatureFlagsStateListView(AnsibleBaseView):
3812
"""
3913
A view class for displaying feature flags
4014
"""
4115

42-
queryset = AAPFlag.objects.order_by('id')
4316
serializer_class = FeatureFlagSerializer
44-
permission_classes = [IsSuperuserOrAuditor]
45-
http_method_names = ['get', 'put', 'head', 'options']
46-
47-
def update(self, request, **kwargs):
48-
_feature_flag = self.get_object()
49-
value = request.data.get('value')
50-
if not value:
51-
return Response(status=status.HTTP_400_BAD_REQUEST, data={"details": "Invalid request object."})
52-
53-
# Disable runtime toggle if the feature flag feature is not enabled
54-
if not flag_enabled('FEATURE_FEATURE_FLAGS_ENABLED'):
55-
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED, data={"details": "Runtime feature flags toggle is not enabled."})
56-
57-
feature_flag = get_object_or_404(AAPFlag, pk=_feature_flag.id)
58-
if feature_flag.toggle_type == 'install-time':
59-
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED, data={"details": "Install-time feature flags cannot be toggled at run-time."})
60-
if feature_flag.condition == "boolean" and not is_boolean_str(value):
61-
return Response(status=status.HTTP_400_BAD_REQUEST, data={"details": "Feature flag boolean conditional requires using boolean value."})
62-
feature_flag.value = value
63-
feature_flag.save()
64-
65-
return Response(self.get_serializer().to_representation(feature_flag))
66-
67-
68-
# TODO: This can be removed after functionality is migrated over to new class
69-
class OldFeatureFlagsStateListView(AnsibleBaseView):
70-
"""
71-
A view class for displaying feature flags
72-
"""
73-
74-
serializer_class = OldFeatureFlagSerializer
7517
filter_backends = []
7618
name = _('Feature Flags')
7719
http_method_names = ['get', 'head']
7820

7921
def get(self, request, format=None):
80-
self.serializer = OldFeatureFlagSerializer()
22+
self.serializer = FeatureFlagSerializer()
8123
return Response(self.serializer.to_representation())
8224

8325
def get_queryset(self):

ansible_base/lib/dynamic_config/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
load_envvars,
66
load_python_file_with_injected_context,
77
load_standard_settings_files,
8-
toggle_database_feature_flags,
98
toggle_feature_flags,
109
validate,
1110
)
@@ -18,6 +17,5 @@
1817
"load_python_file_with_injected_context",
1918
"load_standard_settings_files",
2019
"toggle_feature_flags",
21-
"toggle_database_feature_flags",
2220
"validate",
2321
]

ansible_base/lib/dynamic_config/dynaconf_helpers.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
from dynaconf.loaders.yaml_loader import yaml
1616
from dynaconf.utils.files import glob
1717
from dynaconf.utils.functional import empty
18-
from flags.sources import get_flags
19-
from flags.state import disable_flag, enable_flag
2018

2119
from ansible_base.lib.dynamic_config.settings_logic import get_mergeable_dab_settings
2220

@@ -317,17 +315,3 @@ def toggle_feature_flags(settings: Dynaconf) -> dict[str, Any]:
317315
feature_content[0]["value"] = installer_value
318316
data[f"FLAGS__{feature_name}"] = feature_content
319317
return data
320-
321-
322-
def toggle_database_feature_flags(settings: Dynaconf) -> dict[str, Any]:
323-
"""Toggle FLAGS based on installer settings.
324-
FLAGS is a django-flags formatted dictionary.
325-
Installers will place `FEATURE_SOME_PLATFORM_FLAG_ENABLED=True/False` in the settings file.
326-
This function will update the value in the database with the expected boolean value
327-
"""
328-
for feature_name in get_flags():
329-
if (installer_value := settings.get(feature_name, empty)) is not empty:
330-
if installer_value:
331-
enable_flag(feature_name)
332-
else:
333-
disable_flag(feature_name)

ansible_base/lib/dynamic_config/dynamic_urls.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
globals()[url_type] = []
1212

1313
installed_apps = getattr(settings, 'INSTALLED_APPS', [])
14-
installed_apps_omit_urls = getattr(settings, 'INSTALLED_APPS_OMIT_URLS', [])
1514
for app in installed_apps:
16-
if app.startswith('ansible_base.') and app not in installed_apps_omit_urls:
15+
if app.startswith('ansible_base.'):
1716
if not importlib.util.find_spec(f'{app}.urls'):
1817
logger.debug(f'Module {app} does not specify urls.py')
1918
continue

ansible_base/lib/dynamic_config/feature_flags/platform_flags.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,52 @@
33

44
class AAPFlagNameSchema(TypedDict):
55
name: str
6-
support_level: str
6+
condition: str
7+
value: str
78
visibility: str
8-
toggle_type: str
9+
support_level: str
910
description: str
10-
version_added: str
1111
labels: list
12+
toggle_type: str
1213

1314

1415
AAP_FEATURE_FLAGS: list[AAPFlagNameSchema] = [
1516
AAPFlagNameSchema(
1617
name="FEATURE_FEATURE_FLAGS_ENABLED",
1718
condition="boolean",
18-
value=True,
19+
value="True",
1920
visibility="public",
2021
support_level='READY_FOR_PRODUCTION',
2122
description='If enabled, feature flags can be toggled on/off at runtime via UI or API. '
2223
'If disabled, feature flags can only be toggled on/off at install-time.',
23-
version_added='2.5',
2424
labels=['platform'],
2525
toggle_type='install-time',
2626
),
2727
AAPFlagNameSchema(
2828
name="FEATURE_INDIRECT_NODE_COUNTING_ENABLED",
2929
visibility="public",
3030
condition="boolean",
31-
value=False,
31+
value="False",
3232
support_level="NOT_FOR_PRODUCTION",
3333
description="TBD",
34-
version_added="2.5",
3534
labels=['controller'],
3635
),
3736
AAPFlagNameSchema(
3837
name="FEATURE_POLICY_AS_CODE_ENABLED",
3938
visibility="public",
4039
condition="boolean",
41-
value=False,
40+
value="False",
4241
support_level="NOT_FOR_PRODUCTION",
4342
description="TBD",
44-
version_added="2.5",
4543
labels=['controller'],
4644
),
4745
AAPFlagNameSchema(
4846
name="FEATURE_EDA_ANALYTICS_ENABLED",
4947
condition="boolean",
50-
value=False,
48+
value="False",
5149
visibility="public",
5250
support_level="NOT_FOR_PRODUCTION",
5351
description="TBD",
54-
version_added="2.5",
5552
labels=['eda'],
5653
),
5754
]

ansible_base/resource_registry/shared_types.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,24 @@ class TeamType(SharedResourceTypeSerializer):
7575
default="",
7676
allow_blank=True,
7777
)
78+
79+
80+
class FeatureFlagType(SharedResourceTypeSerializer):
81+
"""Serialize list of feature flags"""
82+
83+
RESOURCE_TYPE = "feature_flag"
84+
UNIQUE_FIELDS = (
85+
"name",
86+
"condition",
87+
"value",
88+
)
89+
90+
name = serializers.CharField()
91+
condition = serializers.CharField()
92+
value = serializers.CharField()
93+
required = serializers.BooleanField()
94+
support_level = serializers.CharField()
95+
visibility = serializers.CharField()
96+
toggle_type = serializers.CharField()
97+
description = serializers.CharField()
98+
labels = serializers.JSONField()

0 commit comments

Comments
 (0)