Skip to content

Commit 6b72afd

Browse files
committed
wip: resource-sync
1 parent aa7647c commit 6b72afd

File tree

17 files changed

+92
-60
lines changed

17 files changed

+92
-60
lines changed

.dockerignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
**/.github
2+
**/.cache
3+
**/.gitignore
4+
**/.venv
5+
**/venv
6+
**/.tox

ansible_base/feature_flags/apps.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,8 @@
11
from django.apps import AppConfig
2-
from django.db.models.signals import post_migrate
3-
from django.db.utils import OperationalError, ProgrammingError
4-
5-
from .utils import create_initial_data
62

73

84
class FeatureFlagsConfig(AppConfig):
95
default_auto_field = 'django.db.models.BigAutoField'
106
name = 'ansible_base.feature_flags'
117
label = 'dab_feature_flags'
128
verbose_name = 'Feature Flags'
13-
14-
def ready(self):
15-
from django.conf import settings
16-
17-
if 'ansible_base.feature_flags' in settings.INSTALLED_APPS:
18-
# TODO: Is there a better way to handle this logic?
19-
20-
# If migrations are complete, attempt to load in feature flags again.
21-
# This can help capture any updates to the platform flags loaded in to ensure that new values
22-
# are added and updated.
23-
# Otherwise wait for migrations to be complete before loading in feature flags.
24-
try:
25-
create_initial_data()
26-
except (ProgrammingError, OperationalError):
27-
post_migrate.connect(create_initial_data)

ansible_base/feature_flags/migrations/0001_initial.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2.17 on 2025-04-08 14:36
1+
# Generated by Django 4.2.17 on 2025-06-04 12:00
22

33
import ansible_base.feature_flags.models.aap_flag
44
from django.conf import settings
@@ -36,7 +36,7 @@ class Migration(migrations.Migration):
3636
('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)),
3737
],
3838
options={
39-
'unique_together': {('name', 'condition', 'value')},
39+
'unique_together': {('name', 'condition')},
4040
},
4141
),
4242
]

ansible_base/feature_flags/models/aap_flag.py

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

5-
from ansible_base.activitystream.models import AuditableModel
65
from ansible_base.lib.abstract_models.common import NamedCommonModel
7-
8-
# from ansible_base.resource_registry.fields import AnsibleResourceField
6+
from ansible_base.resource_registry.fields import AnsibleResourceField
97

108

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

1513

16-
class AAPFlag(NamedCommonModel, AuditableModel):
14+
class AAPFlag(NamedCommonModel):
1715
class Meta:
1816
app_label = "dab_feature_flags"
19-
unique_together = ("name", "condition", "value")
17+
unique_together = ("name", "condition")
2018

2119
def __str__(self):
2220
return "{name} is enabled when {condition} is " "{value}{required}".format(
@@ -26,7 +24,7 @@ def __str__(self):
2624
required=" (required)" if self.required else "",
2725
)
2826

29-
# resource = AnsibleResourceField(primary_key_field="id")
27+
resource = AnsibleResourceField(primary_key_field="id")
3028

3129
name = models.CharField(
3230
max_length=64,

ansible_base/feature_flags/serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class FeatureFlagSerializer(NamedCommonModelSerializer):
1515
class Meta:
1616
model = AAPFlag
1717
fields = NamedCommonModelSerializer.Meta.fields + [x.name for x in AAPFlag._meta.concrete_fields] + ['state']
18-
read_only_fields = ["name", "condition", "required", "support_level", "visibility", "toggle_type", "description", "labels"]
18+
read_only_fields = ["name", "condition", "required", "support_level", "visibility", "toggle_type", "description", "labels", "ui_name", "support_url"]
1919

2020
def get_state(self, instance):
2121
return flag_state(instance.name)

ansible_base/feature_flags/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
router = AssociationResourceRouter()
1010

11+
router.register(r'feature_flags/states', views.FeatureFlagsStatesView, basename='aap_flags_states')
1112
router.register(r'feature_flags', views.FeatureFlagsView, basename='aap_flags')
1213

1314
# TODO: Remove once all components are migrated to new endpoints.

ansible_base/feature_flags/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def load_feature_flags():
5858
AAPFlag(**flag).full_clean()
5959
except ValidationError as e:
6060
# Ignore this error unless better way to bypass this
61-
if e.messages[0] == 'Aap flag with this Name, Condition and Value already exists.':
61+
if e.messages[0] == 'Aap flag with this Name and Condition already exists.':
6262
pass
6363
else:
6464
# Raise row validation errors

ansible_base/feature_flags/views.py

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from django.shortcuts import get_object_or_404
22
from django.utils.translation import gettext_lazy as _
3-
from flags.state import flag_enabled
3+
from flags.sources import get_flags
4+
from flags.state import flag_state
45
from rest_framework import status
56
from rest_framework.response import Response
67
from rest_framework.viewsets import ModelViewSet
@@ -9,7 +10,10 @@
910
from ansible_base.feature_flags.serializers import FeatureFlagSerializer, OldFeatureFlagSerializer
1011
from ansible_base.lib.utils.views.ansible_base import AnsibleBaseView
1112
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.lib.utils.views.permissions import IsSuperuserOrAuditor, try_add_oauth2_scope_permission
14+
15+
# from ansible_base.oauth2_provider.permissions import OAuth2ScopePermission
16+
from ansible_base.rest_pagination import DefaultPaginator
1317

1418
from .utils import get_django_flags, is_boolean_str
1519

@@ -21,24 +25,27 @@ class FeatureFlagsView(AnsibleBaseDjangoAppApiView, ModelViewSet):
2125

2226
queryset = AAPFlag.objects.order_by('id')
2327
serializer_class = FeatureFlagSerializer
24-
permission_classes = [IsSuperuserOrAuditor]
25-
http_method_names = ['get', 'put', 'head', 'options']
26-
27-
def update(self, request, **kwargs):
28-
_feature_flag = self.get_object()
29-
value = request.data.get('value')
30-
if not value:
31-
return Response(status=status.HTTP_400_BAD_REQUEST, data={"details": "Invalid request object."})
32-
33-
feature_flag = get_object_or_404(AAPFlag, pk=_feature_flag.id)
34-
if feature_flag.toggle_type == 'install-time':
35-
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED, data={"details": "Install-time feature flags cannot be toggled at run-time."})
36-
if feature_flag.condition == "boolean" and not is_boolean_str(value):
37-
return Response(status=status.HTTP_400_BAD_REQUEST, data={"details": "Feature flag boolean conditional requires using boolean value."})
38-
feature_flag.value = value
39-
feature_flag.save()
40-
41-
return Response(self.get_serializer().to_representation(feature_flag))
28+
permission_classes = try_add_oauth2_scope_permission([IsSuperuserOrAuditor])
29+
http_method_names = ['get', 'head', 'options']
30+
31+
32+
class FeatureFlagsStatesView(AnsibleBaseDjangoAppApiView, ModelViewSet):
33+
"""
34+
A view class for displaying feature flags states
35+
"""
36+
37+
queryset = AAPFlag.objects.order_by('id')
38+
permission_classes = try_add_oauth2_scope_permission([IsSuperuserOrAuditor])
39+
http_method_names = ['get', 'head', 'options']
40+
41+
def list(self, request):
42+
paginator = DefaultPaginator()
43+
flags = get_flags()
44+
ret = []
45+
for flag in flags:
46+
ret.append({"flag_name": flag, "flag_state": flag_state(flag)})
47+
result_page = paginator.paginate_queryset(ret, request)
48+
return paginator.get_paginated_response(result_page)
4249

4350

4451
# TODO: This can be removed after functionality is migrated over to new class

ansible_base/lib/dynamic_config/feature_flags/platform_flags.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,26 @@ class AAPFlagNameSchema(TypedDict):
4949
support_url="https://docs.redhat.com/en/documentation/red_hat_ansible_automation_platform/2.5/",
5050
labels=['eda'],
5151
),
52+
AAPFlagNameSchema(
53+
name="FEATURE_GATEWAY_IPV6_USAGE_ENABLED",
54+
ui_name="Gateway IPv6 Usage",
55+
condition="boolean",
56+
value="False",
57+
visibility="private",
58+
support_level="NOT_FOR_PRODUCTION",
59+
description="TBD",
60+
support_url="https://docs.redhat.com/en/documentation/red_hat_ansible_automation_platform/2.5/",
61+
labels=['gateway'],
62+
),
63+
AAPFlagNameSchema(
64+
name="FEATURE_GATEWAY_CREATE_CRC_SERVICE_TYPE",
65+
ui_name="Gateway Create CRC Service Type",
66+
condition="boolean",
67+
value="False",
68+
visibility="private",
69+
support_level="NOT_FOR_PRODUCTION",
70+
description="TBD",
71+
support_url="https://docs.redhat.com/en/documentation/red_hat_ansible_automation_platform/2.5/",
72+
labels=['gateway'],
73+
),
5274
]

ansible_base/rbac/permission_registry.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def create_managed_roles(self, apps) -> list[tuple[Model, bool]]:
143143
return ret
144144

145145
def call_when_apps_ready(self, apps, app_config) -> None:
146+
from ansible_base.feature_flags.utils import create_initial_data as feature_flag_create_initial_data
146147
from ansible_base.rbac import triggers
147148
from ansible_base.rbac.evaluations import bound_has_obj_perm, bound_singleton_permissions, connect_rbac_methods
148149
from ansible_base.rbac.management import create_dab_permissions
@@ -172,6 +173,11 @@ def call_when_apps_ready(self, apps, app_config) -> None:
172173
sender=app_config,
173174
dispatch_uid="ansible_base.rbac.triggers.post_migration_rbac_setup",
174175
)
176+
if 'ansible_base.feature_flags' in settings.INSTALLED_APPS:
177+
try:
178+
feature_flag_create_initial_data()
179+
except Exception:
180+
post_migrate.connect(feature_flag_create_initial_data, sender=self)
175181

176182
self.user_model.add_to_class('has_obj_perm', bound_has_obj_perm)
177183
self.user_model.add_to_class('singleton_permissions', bound_singleton_permissions)

0 commit comments

Comments
 (0)