-
Notifications
You must be signed in to change notification settings - Fork 86
AAP-45875 Runtime Feature Flags #875
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
f2333da
WIP: Runtime feature flags
zkayyali812 1b9c2cd
attempt moving apis to gw
zkayyali812 7e01bf6
Update DAB PoC
zkayyali812 68bb4ca
Add feature flag management command
zkayyali812 482ec99
resource-sync
zkayyali812 e05f2bc
refactor and cleanup code
zkayyali812 8b6bb3d
add unit tests
zkayyali812 4dc16fb
Refactor view to gateway
zkayyali812 233a678
Change flags list to yaml list
zkayyali812 e7941f2
Add documentation and JSON Schema validation
zkayyali812 032633f
Adjust feature flag metadata
zkayyali812 297d8b6
Fix post-migrate call. Replace with manual migrations
zkayyali812 1c96430
Add missing FEATURE_CASE_INSENSITIVE_AUTH_MAPS_ENABLED flag and fix t…
fao89 60caf7b
Fix resource sync orphan detection to exclude system user
fao89 f8cb592
Merge branch 'devel' into phase2/feature-flags/poc
bhavenst File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| **/.github | ||
| **/.cache | ||
| **/.gitignore | ||
| **/.venv | ||
| **/venv | ||
| **/.tox |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,14 @@ | ||
| from django.apps import AppConfig | ||
| from django.db.models.signals import post_migrate | ||
|
|
||
| from ansible_base.feature_flags.utils import create_initial_data | ||
|
|
||
|
|
||
| class FeatureFlagsConfig(AppConfig): | ||
| default_auto_field = 'django.db.models.BigAutoField' | ||
| name = 'ansible_base.feature_flags' | ||
| label = 'dab_feature_flags' | ||
| verbose_name = 'Feature Flags' | ||
|
|
||
| def ready(self): | ||
| post_migrate.connect(create_initial_data, sender=self) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| - name: FEATURE_INDIRECT_NODE_COUNTING_ENABLED | ||
| ui_name: Indirect Node Counting | ||
| visibility: True | ||
| condition: boolean | ||
| value: 'False' | ||
| support_level: TECHNOLOGY_PREVIEW | ||
| description: "Indirect Node Counting parses the event stream of all jobs to identify resources and stores these in the platform database. Example: Job automates VMware, the parser will report back the VMs, Hypervisors that were automated. This feature helps customers and partners report on the automations they are doing beyond an API endpoint." | ||
| support_url: https://access.redhat.com/articles/7109910 | ||
| labels: | ||
| - controller | ||
| - name: FEATURE_EDA_ANALYTICS_ENABLED | ||
| ui_name: Event-Driven Ansible Analytics | ||
| visibility: False | ||
| condition: boolean | ||
| value: 'False' | ||
| support_level: TECHNOLOGY_PREVIEW | ||
| description: Submit Event-Driven Ansible usage analytics to console.redhat.com. | ||
| support_url: https://access.redhat.com/solutions/7112810 | ||
| toggle_type: install-time | ||
| labels: | ||
| - eda | ||
| - name: FEATURE_GATEWAY_IPV6_USAGE_ENABLED | ||
| ui_name: Gateway IPv6 Enablement | ||
| visibility: False | ||
| condition: boolean | ||
| value: 'True' | ||
| support_level: TECHNOLOGY_PREVIEW | ||
| description: The feature flag represents enabling IPv6 only traffic to be allowed through the gateway component and does not include all components of the platform. | ||
| support_url: https://access.redhat.com/articles/7116569 | ||
| labels: | ||
| - gateway | ||
| - name: FEATURE_GATEWAY_CREATE_CRC_SERVICE_TYPE_ENABLED | ||
| ui_name: Dynamic Service Type Feature | ||
| visibility: False | ||
| condition: boolean | ||
| value: 'False' | ||
| support_level: DEVELOPER_PREVIEW | ||
| description: The Dynamic Service Type feature allows for the introduction of new platform services without requiring registration to the existing database. The new service can be enabled through the use of configuration. | ||
| support_url: https://access.redhat.com/articles/7122668 | ||
| toggle_type: install-time | ||
| labels: | ||
| - gateway | ||
| - name: FEATURE_DISPATCHERD_ENABLED | ||
| ui_name: AAP Dispatcherd background tasking system | ||
| visibility: False | ||
| condition: boolean | ||
| value: 'False' | ||
| support_level: TECHNOLOGY_PREVIEW | ||
| description: A service to run python tasks in subprocesses, designed specifically to work well with pg_notify, but intended to be extensible to other message delivery means. | ||
| support_url: '' | ||
| toggle_type: install-time | ||
| labels: | ||
| - eda | ||
| - controller | ||
fao89 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - name: FEATURE_CASE_INSENSITIVE_AUTH_MAPS_ENABLED | ||
| ui_name: Case Insensitive Authentication Maps | ||
| visibility: False | ||
| condition: boolean | ||
| value: 'True' | ||
| support_level: DEVELOPER_PREVIEW | ||
| description: Enable case-insensitive comparison for authentication mapping attributes and group names. | ||
| support_url: '' | ||
| labels: | ||
| - platform | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| { | ||
| "$schema": "http://json-schema.org/draft-07/schema#", | ||
| "title": "Feature Flag Configuration Schema", | ||
| "description": "Validates a list of feature flag configurations.", | ||
| "type": "array", | ||
| "items": { | ||
| "type": "object", | ||
| "properties": { | ||
| "name": { | ||
| "description": "The unique identifier for the feature flag. Must be in all capitals and start with 'FEATURE_' and end with '_ENABLED'", | ||
| "type": "string", | ||
| "pattern": "^FEATURE_[A-Z0-9_]+_ENABLED$" | ||
| }, | ||
| "ui_name": { | ||
| "description": "The human-readable name for the feature flag displayed in the UI.", | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "visibility": { | ||
| "description": "Controls whether the feature is visible in the UI.", | ||
| "type": "boolean" | ||
| }, | ||
| "condition": { | ||
| "description": "The type of condition for the feature flag's value. Currently only boolean is supported.", | ||
| "type": "string", | ||
| "enum": ["boolean"] | ||
| }, | ||
| "value": { | ||
| "description": "The default value of the feature flag, as a string.", | ||
| "type": "string", | ||
| "enum": ["True", "False"] | ||
| }, | ||
| "support_level": { | ||
| "description": "The level of support provided for this feature.", | ||
| "type": "string", | ||
| "enum": [ | ||
| "TECHNOLOGY_PREVIEW", | ||
| "DEVELOPER_PREVIEW" | ||
| ] | ||
| }, | ||
| "description": { | ||
| "description": "A brief explanation of what the feature does.", | ||
| "type": "string" | ||
| }, | ||
| "support_url": { | ||
| "description": "A URL to the relevant documentation for the feature.", | ||
| "type": "string", | ||
| "format": "uri" | ||
| }, | ||
| "toggle_type": { | ||
| "description": "The actual value of the feature flag. Note: The YAML string 'False' or 'True' is parsed as a boolean.", | ||
| "type": "string", | ||
| "enum": ["install-time", "run-time"] | ||
| }, | ||
| "labels": { | ||
| "description": "A list of labels to categorize the feature.", | ||
| "type": "array", | ||
| "items": { | ||
| "type": "string", | ||
| "enum": ["controller", "eda", "gateway", "platform"] | ||
| }, | ||
| "minItems": 1, | ||
| "uniqueItems": true | ||
| } | ||
| }, | ||
| "required": [ | ||
| "name", | ||
| "ui_name", | ||
| "visibility", | ||
| "condition", | ||
| "value", | ||
| "support_level", | ||
| "description", | ||
| "support_url" | ||
| ] | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| from django.apps import apps | ||
| from flags.sources import Condition | ||
|
|
||
|
|
||
| class DatabaseCondition(Condition): | ||
| """Condition that includes the AAPFlags database object | ||
| This is required to ensure that enable_flag/disable_flag calls | ||
| can work as expected, with the custom flag objects | ||
| """ | ||
|
|
||
| def __init__(self, condition, value, required=False, obj=None): | ||
| super().__init__(condition, value, required=required) | ||
| self.obj = obj | ||
|
|
||
|
|
||
| class AAPFlagSource(object): | ||
| """The customer AAP flag source, retrieves a list of all flags in the database""" | ||
|
|
||
| def get_queryset(self): | ||
| aap_flags = apps.get_model('dab_feature_flags', 'AAPFlag') | ||
| return aap_flags.objects.all() | ||
|
|
||
| def get_flags(self): | ||
| flags = {} | ||
| for o in self.get_queryset(): | ||
| if o.name not in flags: | ||
| flags[o.name] = [] | ||
| flags[o.name].append(DatabaseCondition(o.condition, o.value, required=o.required, obj=o)) | ||
| return flags |
Empty file.
Empty file.
50 changes: 50 additions & 0 deletions
50
ansible_base/feature_flags/management/commands/feature_flags.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| try: | ||
| from tabulate import tabulate | ||
|
|
||
| HAS_TABULATE = True | ||
| except ImportError: | ||
| HAS_TABULATE = False | ||
|
|
||
| from django.core.management.base import BaseCommand | ||
| from flags.state import flag_state | ||
|
|
||
| from ansible_base.feature_flags.models import AAPFlag | ||
|
|
||
|
|
||
| class Command(BaseCommand): | ||
| help = "AAP Feature Flag management command" | ||
|
|
||
| def add_arguments(self, parser): | ||
| parser.add_argument("--list", action="store_true", help="List feature flags", required=False) | ||
|
|
||
| def handle(self, *args, **options): | ||
| if options["list"]: | ||
| self.list_feature_flags() | ||
|
|
||
| def list_feature_flags(self): | ||
| feature_flags = [] | ||
| headers = ["Name", "UI_Name", "Value", "State", "Support Level", "Visibility", "Toggle Type", "Description", "Support URL"] | ||
|
|
||
| for feature_flag in AAPFlag.objects.all().order_by('name'): | ||
| feature_flags.append( | ||
| [ | ||
| f'{feature_flag.name}', | ||
| f'{feature_flag.ui_name}', | ||
| f'{feature_flag.value}', | ||
| f'{flag_state(feature_flag.name)}', | ||
| f'{feature_flag.support_level}', | ||
| f'{feature_flag.visibility}', | ||
| f'{feature_flag.toggle_type}', | ||
| f'{feature_flag.description}', | ||
| f'{feature_flag.support_url}', | ||
| ] | ||
| ) | ||
| self.stdout.write('') | ||
|
|
||
| if HAS_TABULATE: | ||
| self.stdout.write(tabulate(feature_flags, headers, tablefmt="github")) | ||
| else: | ||
| self.stdout.write("\t".join(headers)) | ||
| for feature_flag in feature_flags: | ||
| self.stdout.write("\t".join(feature_flag)) | ||
| self.stdout.write('') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Generated by Django 4.2.21 on 2025-06-24 13:34 | ||
| # FileHash: 8d069529c20fce63ddd45711b014cbb6603be30c2d45108b10881480c49cfbcc | ||
|
|
||
| import ansible_base.feature_flags.models.aap_flag | ||
| from django.conf import settings | ||
| from django.db import migrations, models | ||
| import django.db.models.deletion | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| initial = True | ||
|
|
||
| dependencies = [ | ||
| migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name='AAPFlag', | ||
| fields=[ | ||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
| ('modified', models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.')), | ||
| ('created', models.DateTimeField(auto_now_add=True, help_text='The date/time this resource was created.')), | ||
| ('name', models.CharField(help_text='The name of the feature flag. Must follow the format of FEATURE_<flag-name>_ENABLED.', max_length=64, validators=[ansible_base.feature_flags.models.aap_flag.validate_feature_flag_name])), | ||
| ('ui_name', models.CharField(help_text='The pretty name to display in the application User Interface', max_length=64)), | ||
| ('condition', models.CharField(default='boolean', help_text='Used to specify a condition, which if met, will enable the feature flag.', max_length=64)), | ||
| ('value', models.CharField(default='False', help_text='The value used to evaluate the conditional specified.', max_length=127)), | ||
| ('required', models.BooleanField(default=False, help_text="If multiple conditions are required to be met to enable a feature flag, 'required' can be used to specify the necessary conditionals.")), | ||
| ('support_level', models.CharField(choices=[('DEVELOPER_PREVIEW', 'Developer Preview'), ('TECHNOLOGY_PREVIEW', 'Technology Preview')], editable=False, help_text='The support criteria for the feature flag. Must be one of (DEVELOPER_PREVIEW or TECHNOLOGY_PREVIEW).', max_length=25)), | ||
| ('visibility', models.BooleanField(default=False, help_text='Controls whether the feature is visible in the UI.')), | ||
| ('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)), | ||
| ('description', models.CharField(default='', help_text='A detailed description giving an overview of the feature flag.', max_length=500)), | ||
| ('support_url', models.CharField(blank=True, default='', help_text='A link to the documentation support URL for the feature', max_length=250)), | ||
| ('labels', models.JSONField(blank=True, default=list, help_text='A list of labels for the feature flag.', null=True, validators=[ansible_base.feature_flags.models.aap_flag.validate_labels])), | ||
| ('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)), | ||
| ('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)), | ||
| ], | ||
| options={ | ||
| 'unique_together': {('name', 'condition')}, | ||
| }, | ||
| ), | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| ### INSTRUCTIONS ### | ||
| # If updating the feature_flags.yaml, create a new migration file by copying this one. | ||
| # 1. Name the file XXXX_manual_YYYYMMDD.py. For example 0002_manual_20250808.py | ||
| # 1. Uncomment the migration below, by uncommenting everything below the FileHash | ||
| # 2. Update the dependency to point to the last dependency | ||
| # 3. Set the FileHash | ||
| ### | ||
fao89 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # FileHash: <FileHash> | ||
|
|
||
| # from django.db import migrations | ||
|
|
||
|
|
||
| # class Migration(migrations.Migration): | ||
|
|
||
| # dependencies = [ | ||
| # ('dab_feature_flags', '0001_initial'), | ||
| # ] | ||
|
|
||
| # operations = [ | ||
| # ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| from .aap_flag import AAPFlag | ||
|
|
||
| __all__ = ('AAPFlag',) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.