Skip to content

Commit f492546

Browse files
committed
Add documentation and JSON Schema validation
1 parent e63bb51 commit f492546

File tree

6 files changed

+141
-38
lines changed

6 files changed

+141
-38
lines changed
File renamed without changes.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "Feature Flag Configuration Schema",
4+
"description": "Validates a list of feature flag configurations.",
5+
"type": "array",
6+
"items": {
7+
"type": "object",
8+
"properties": {
9+
"name": {
10+
"description": "The unique identifier for the feature flag. Must be in all capitals and start with 'FEATURE_' and end with '_ENABLED'",
11+
"type": "string",
12+
"pattern": "^FEATURE_[A-Z0-9_]+_ENABLED$"
13+
},
14+
"ui_name": {
15+
"description": "The human-readable name for the feature flag displayed in the UI.",
16+
"type": "string",
17+
"minLength": 1
18+
},
19+
"visibility": {
20+
"description": "Controls whether the feature is visible in the UI.",
21+
"type": "string",
22+
"enum": ["public", "private"]
23+
},
24+
"condition": {
25+
"description": "The type of condition for the feature flag's value. Currently only boolean is supported.",
26+
"type": "string",
27+
"enum": ["boolean"]
28+
},
29+
"value": {
30+
"description": "The default value of the feature flag, as a string.",
31+
"type": "string",
32+
"enum": ["True", "False"]
33+
},
34+
"support_level": {
35+
"description": "The level of support provided for this feature.",
36+
"type": "string",
37+
"enum": [
38+
"NOT_FOR_USE",
39+
"NOT_FOR_PRODUCTION",
40+
"READY_FOR_PRODUCTION"
41+
]
42+
},
43+
"description": {
44+
"description": "A brief explanation of what the feature does.",
45+
"type": "string"
46+
},
47+
"support_url": {
48+
"description": "A URL to the relevant documentation for the feature.",
49+
"type": "string",
50+
"format": "uri"
51+
},
52+
"toggle_type": {
53+
"description": "The actual value of the feature flag. Note: The YAML string 'False' or 'True' is parsed as a boolean.",
54+
"type": "string",
55+
"enum": ["install-time", "run-time"]
56+
},
57+
"labels": {
58+
"description": "A list of labels to categorize the feature.",
59+
"type": "array",
60+
"items": {
61+
"type": "string",
62+
"enum": ["controller", "eda", "gateway", "platform"]
63+
},
64+
"minItems": 1,
65+
"uniqueItems": true
66+
}
67+
},
68+
"required": [
69+
"name",
70+
"ui_name",
71+
"visibility",
72+
"condition",
73+
"value",
74+
"support_level",
75+
"description",
76+
"support_url"
77+
]
78+
}
79+
}

ansible_base/feature_flags/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def get_django_flags():
1616

1717
def feature_flags_list():
1818
current_dir = Path(__file__).parent
19-
flags_list_file = current_dir / 'feature_flags.yaml'
19+
flags_list_file = current_dir / 'definitions/feature_flags.yaml'
2020
with open(flags_list_file, 'r') as file:
2121
try:
2222
return yaml.safe_load(file)

docs/apps/feature_flags.md

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,32 @@ Additional library documentation can be found at https://cfpb.github.io/django-f
55

66
## Settings
77

8-
Add `ansible_base.feature_flags` to your installed apps:
8+
Add `ansible_base.feature_flags` to your installed apps and ensure `ansible_base.resource_registry` as added to enable flag state to sync across the platform:
99

1010
```python
1111
INSTALLED_APPS = [
1212
...
1313
'ansible_base.feature_flags',
14+
'ansible_base.resource_registry', # Must also be added
1415
]
1516
```
1617

17-
### Additional Settings
18+
## Detail
1819

19-
Additional settings are required to enable feature_flags.
20-
This will happen automatically if using [dynamic_settings](../Installation.md)
21-
22-
First, you need to add `flags` to your `INSTALLED_APPS`:
20+
By adding the `ansible_base.feature_flags` app to your application, all Ansible Automation Platform feature flags will be loaded and available in your component.
21+
To receive flag state updates, ensure the following definition is available in your components `RESOURCE_LIST` -
2322

2423
```python
25-
INSTALLED_APPS = [
26-
...
27-
'flags',
28-
...
29-
]
30-
```
31-
32-
Additionally, create a `FLAGS` entry:
33-
34-
```python
35-
FLAGS = {}
36-
```
37-
38-
Finally, add `django.template.context_processors.request` to your `TEMPLATES` `context_processors` setting:
24+
from ansible_base.feature_flags.models import AAPFlag
25+
from ansible_base.resource_registry.shared_types import FeatureFlagType
3926

40-
```python
41-
TEMPLATES = [
42-
{
43-
'BEACKEND': 'django.template.backends.django.DjangoTemplates',
44-
...
45-
'OPTIONS': {
46-
...
47-
'context_processors': [
48-
...
49-
'django.template.context_processors.request',
50-
...
51-
]
52-
...
53-
}
54-
...
55-
}
56-
]
27+
RESOURCE_LIST = (
28+
...
29+
ResourceConfig(
30+
AAPFlag,
31+
shared_resource=SharedResource(serializer=FeatureFlagType, is_provider=False),
32+
),
33+
)
5734
```
5835

5936
## URLS
@@ -70,3 +47,29 @@ urlpatterns = [
7047
...
7148
]
7249
```
50+
51+
## Adding Feature Flags
52+
53+
To add a feature flag to the platform, specify it in the following [file](../../ansible_base/feature_flags/definitions/feature_flags.yaml)
54+
55+
An example flag could resemble -
56+
57+
```yaml
58+
- name: FEATURE_FOO_ENABLED
59+
ui_name: Foo
60+
visibility: public
61+
condition: boolean
62+
value: 'False'
63+
support_level: NOT_FOR_PRODUCTION
64+
description: TBD
65+
support_url: https://docs.redhat.com/en/documentation/red_hat_ansible_automation_platform/2.5/
66+
labels:
67+
- controller
68+
```
69+
70+
Validate this file against the json schema by running `check-jsonschema` -
71+
72+
```bash
73+
pip install check-jsonschema
74+
check-jsonschema --schemafile ansible_base/feature_flags/definitions/schema.json ansible_base/feature_flags/definitions/feature_flags.yaml
75+
```

requirements/requirements_dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ flake8==7.1.1 # Linting tool, if changed update pyproject.toml as well
99
Flake8-pyproject==1.2.3 # Linting tool, if changed update pyproject.toml as well
1010
ipython
1111
isort==6.0.0 # Linting tool, if changed update pyproject.toml as well
12+
jsonschema
1213
tox
1314
tox-docker
1415
typeguard

test_app/tests/feature_flags/test_utils.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import json
12
from unittest.mock import MagicMock, call
23

34
import pytest
5+
import yaml
46
from django.core.exceptions import ValidationError
7+
from jsonschema import validate
58

69
MODULE_PATH = "ansible_base.feature_flags.utils"
710

@@ -119,6 +122,23 @@ def test_get_django_flags(mocker):
119122
assert result == {"FLAG_X": True}
120123

121124

125+
def test_validate_flags_yaml_against_json_schema():
126+
feature_flags_yaml = 'ansible_base/feature_flags/definitions/feature_flags.yaml'
127+
feature_flags_schema = 'ansible_base/feature_flags/definitions/schema.json'
128+
try:
129+
with open(feature_flags_yaml, 'r') as file:
130+
feature_flags_file = yaml.safe_load(file)
131+
with open(feature_flags_schema, 'r') as file:
132+
schema = json.load(file)
133+
validate(instance=feature_flags_file, schema=schema)
134+
assert True, "Validation succeeded as expected."
135+
except FileNotFoundError as e:
136+
pytest.fail(f"Could not find a necessary file: {e}. Make sure schema.json and valid_data.yaml exist.")
137+
except Exception as e:
138+
# If any other exception occurs (like a ValidationError), fail the test.
139+
pytest.fail(f"Validation failed unexpectedly for a valid file: {e}")
140+
141+
122142
class TestCreateInitialData:
123143

124144
@pytest.mark.django_db # May not be strictly necessary with all the mocking, but good practice

0 commit comments

Comments
 (0)