Skip to content

Commit 35a14e5

Browse files
mvicknrclaude
andcommitted
fix: Update to use global settings on push
Co-Authored-By: Claude <noreply@anthropic.com>
1 parent aa0c98b commit 35a14e5

12 files changed

Lines changed: 3595 additions & 878 deletions
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
# Agent Config Schema Generator
2+
3+
This directory contains the Python scripts that walk
4+
`newrelic.core.config.global_settings()` to produce a JSON Schema
5+
(`../schemas/config.json`) and to manage version bumps in
6+
`../configurationDefinitions.yml` for Fleet Control.
7+
8+
## Files
9+
10+
| File | Description |
11+
|------|-------------|
12+
| `generate-schema.py` | Per-push regenerator. Reads the live agent settings tree, writes `config.json`. Never touches `configurationDefinitions.yml`. |
13+
| `bump-schema-version.py` | Release-time version bumper. Compares the schema at a prior git ref to the current schema and writes a new version into `configurationDefinitions.yml`. |
14+
| `schema_diff.py` | Shared library (no `main`). Holds the diff classification (`classify_changes`), bump arithmetic (`recommend_bump`, `apply_bump`, `bump_version`), and schema loading (`load_existing`). Imported by both top-level scripts. |
15+
| `dump-settings.py` | Dev helper. Lists every leaf in `global_settings()` and how it appears in the generated schema (or why it was excluded). Not part of the workflow. |
16+
| `tests/test_generate_schema.py` | Tests for the generator (`infer_type`, `make_property`, `build_properties`, `generate_schema`, anyOf helpers). |
17+
| `tests/test_schema_diff.py` | Tests for the shared library (`classify_changes`, `recommend_bump`, `apply_bump`, `bump_version`, `load_existing`). |
18+
| `tests/test_bump_schema_version.py` | Tests for the bump script (parsing helpers + main bootstrap/happy paths with mocked `git_show`). |
19+
| `../schemas/config.json` | Generated JSON Schema (Draft 2020-12). |
20+
| `../configurationDefinitions.yml` | Fleet Control metadata, including the schema's semver version. Bumped only at release time. |
21+
22+
## How the generator works
23+
24+
The agent's live settings tree (`newrelic.core.config.global_settings()`)
25+
is the source of truth for which keys exist, their types, and their
26+
defaults. `newrelic/newrelic.ini` is consulted only for descriptions
27+
(comments adjacent to `key = value` lines in the `[newrelic]` section).
28+
29+
`generate-schema.py`:
30+
31+
1. Imports `newrelic.core.config` and walks every leaf whose containing
32+
class name ends in `Settings`.
33+
2. Loads descriptions from `newrelic/newrelic.ini`.
34+
3. For every leaf, applies (in order): `TYPE_OVERRIDES`, `ENUM_OVERRIDES`,
35+
set-typed auto-anyOf, type inference from the live value.
36+
4. Skips leaves whose path matches `EXCLUDE_KEYS` (exact or `prefix.*`).
37+
5. Validates the result against the JSON Schema Draft 2020-12 meta-schema.
38+
6. Deep-merges the freshly generated schema into the existing on-disk
39+
`config.json` so the published schema only ever grows.
40+
7. Writes `config.json` and prints a classified diff summary.
41+
42+
The generator does **not** touch `configurationDefinitions.yml` --
43+
version bumps live in the next section.
44+
45+
## How versioning works
46+
47+
Schema regeneration runs **per push** on feature branches via
48+
`.github/workflows/fleet-control-schema.yml`. It writes `config.json`
49+
and nothing else. Reviewers see schema diffs in PRs.
50+
51+
Version bumps run **manually before each release** via
52+
`.github/workflows/fleet-control-schema-bump.yml`, which is
53+
`workflow_dispatch`-only. The bump workflow:
54+
55+
1. Finds the latest `v*` tag on `main` (overridable via the
56+
`since_ref` workflow input).
57+
2. Reads the historical `configurationDefinitions.yml` from that tag --
58+
the version stored there is the **starter version** for the bump.
59+
3. Reads the historical schema using the path declared in that file's
60+
`schema:` field.
61+
4. Compares the historical schema to the current `config.json` on `main`,
62+
classifies the cumulative diff, and applies the recommended bump kind
63+
(major/minor/patch).
64+
5. Opens a PR titled `chore: bump agent config schema version` for team
65+
review.
66+
67+
If the latest release tag predates the schema (the `.fleetControl/`
68+
directory or the `schema:` field in `configurationDefinitions.yml`),
69+
`bump-schema-version.py` exits 0 with a bootstrap message and no PR
70+
is opened. The first release that includes the schema ships at whatever
71+
version is currently in `configurationDefinitions.yml`.
72+
73+
### Release ordering -- run the bump workflow before cutting the tag
74+
75+
The bump PR is a separate review/merge step from the agent's `vX.Y.Z`
76+
release tag. Run the workflows in this order:
77+
78+
1. Trigger `Fleet Control Config Schema Bump` (manual `workflow_dispatch`).
79+
2. Wait for the PR to open (or the workflow to report that no bump is needed).
80+
3. Review and merge the bump PR if one was opened.
81+
4. Cut the GitHub Release from the post-merge `main`.
82+
83+
If the release tag is cut before the bump PR merges, the tag's
84+
`configurationDefinitions.yml` will still say the pre-bump version,
85+
even though the schema itself (`config.json`) at that tag reflects the
86+
new keys. Consumers see a mismatch. The next release will compute its
87+
bump correctly from this tag's metadata, but the tag itself ships
88+
mismatched.
89+
90+
## Quick start
91+
92+
Regenerate the schema, run tests, and surface excluded settings in one
93+
command:
94+
95+
```bash
96+
tox -e fleet-schema
97+
```
98+
99+
This runs the unit tests, regenerates `.fleetControl/schemas/config.json`
100+
(deep-merged into the existing schema), prints the classified diff, and
101+
dumps any settings that didn't make it into the schema. Exit code matches
102+
`generate-schema.py`: `0` if no schema changes, `1` if the schema changed
103+
(commit before pushing), `2` on a hard failure.
104+
105+
> **Run with a clean shell.** `import newrelic.core.config` reads
106+
> `NEW_RELIC_*` env vars at import time and bakes them into the live
107+
> defaults. The tox env unsets them defensively; if you invoke the
108+
> generator directly, do the same (`env -i PATH="$PATH" HOME="$HOME"
109+
> python3 ...`).
110+
111+
### Lower-level commands
112+
113+
If you want to invoke a single step directly without going through tox:
114+
115+
```bash
116+
# Regenerate schema only (from repo root)
117+
python3 .fleetControl/schemaGeneration/generate-schema.py
118+
119+
# Force-regenerate without comparing to existing on-disk schema
120+
python3 .fleetControl/schemaGeneration/generate-schema.py --force
121+
122+
# Dry-run a release-time bump against a tag
123+
python3 .fleetControl/schemaGeneration/bump-schema-version.py --since=v10.21.0
124+
125+
# Apply a release-time bump (writes configurationDefinitions.yml)
126+
python3 .fleetControl/schemaGeneration/bump-schema-version.py --since=v10.21.0 --ci
127+
128+
# Dump every live setting alongside how it appears in the schema
129+
python3 .fleetControl/schemaGeneration/dump-settings.py
130+
131+
# Filter the dump to settings missing from the schema
132+
python3 .fleetControl/schemaGeneration/dump-settings.py --missing
133+
```
134+
135+
## Adding new configuration keys
136+
137+
When new settings land in `newrelic/core/config.py`, the generator
138+
picks them up automatically on the next push -- no manual schema edit
139+
needed for the common case.
140+
141+
**Special handling is required for certain key types**, configured via
142+
override maps in `generate-schema.py`.
143+
144+
### Array-or-string keys (`_environ_as_set`-backed)
145+
146+
Many agent config keys accept either a structured array OR a delimited
147+
string (the INI form):
148+
149+
```ini
150+
attributes.include = request.parameters.* response.headers.content-type
151+
```
152+
153+
These keys are parsed via `_environ_as_set` /
154+
`_environ_as_comma_separated_set` in `newrelic/core/config.py`. For the
155+
JSON Schema to correctly represent both forms, set-typed live values
156+
auto-detect into the right shape -- you don't need a per-key entry
157+
unless the live default is empty (in which case `set` vs. `list` cannot
158+
be distinguished from the value alone). Empty defaults need an explicit
159+
override:
160+
161+
```python
162+
'new_feature.include': string_array_or_delimited(default=[]),
163+
'new_feature.exclude': string_array_or_delimited(default=[]),
164+
```
165+
166+
The auto-detection covers the long tail (the seven `*.attributes.*`
167+
subtrees, `opentelemetry.traces.*`, etc.) because their live values
168+
arrive as Python `set` objects.
169+
170+
### Status code keys
171+
172+
Keys that accept integers, arrays of integers, or range strings (e.g.,
173+
`"100-102 200-208 226 300-308 404"`) should use:
174+
175+
```python
176+
'error_collector.new_status_codes': status_code_array_or_range(),
177+
'error_collector.new_status_codes_with_default': status_code_array_or_range(default=[404]),
178+
```
179+
180+
### Enum keys
181+
182+
Keys with a fixed set of allowed values should be added to `ENUM_OVERRIDES`:
183+
184+
```python
185+
ENUM_OVERRIDES = {
186+
'new_feature.mode': ['option1', 'option2', 'option3'],
187+
}
188+
```
189+
190+
### None-defaulted leaves
191+
192+
Settings whose live default is `None` cannot have their type inferred,
193+
so they're skipped from the schema with a warning. To surface them, add
194+
an explicit type in `TYPE_OVERRIDES`:
195+
196+
```python
197+
'proxy_user': {'type': 'string'},
198+
```
199+
200+
## Excluding keys
201+
202+
Add keys to `EXCLUDE_KEYS` to drop them from the schema:
203+
204+
```python
205+
EXCLUDE_KEYS = {
206+
'agent_run_id', # exact match
207+
'cross_application_tracer.*', # subtree exclusion
208+
}
209+
```
210+
211+
The `.*` suffix matches both the prefix itself and any descendant.
212+
213+
## Checklist for new config keys
214+
215+
1. Add the setting to `newrelic/core/config.py` as you would any other.
216+
2. **Run the generator locally** (`python3 .fleetControl/schemaGeneration/generate-schema.py`) to pick up the new key.
217+
3. **Check the inferred type** in the generated schema.
218+
4. **If the live default is `None`** → add an entry to `TYPE_OVERRIDES`.
219+
5. **If the key uses `_environ_as_set` and the default is empty** → add to `TYPE_OVERRIDES` with `string_array_or_delimited()`.
220+
6. **If the key has enum values** → add to `ENUM_OVERRIDES`.
221+
7. **If the key should be hidden** → add to `EXCLUDE_KEYS`.
222+
8. **Run the generator again**; verify the schema entry looks correct.
223+
9. **Run the tests** (`python3 -m unittest discover .fleetControl/schemaGeneration/tests`).
224+
10. The next release will pick up the bump when the maintainer runs the
225+
bump workflow as part of release prep.
226+
227+
## CLI options
228+
229+
### Generator CLI (`generate-schema.py`)
230+
231+
| Option | Description |
232+
|--------|-------------|
233+
| `--force` | Overwrite the schema without comparing to the existing one. Always exits 0. |
234+
235+
### Bumper CLI (`bump-schema-version.py`)
236+
237+
| Option | Description |
238+
|--------|-------------|
239+
| `--since=<ref>` | Required. Compare the current schema to the schema at `<ref>` and recommend a bump. |
240+
| `--ci` | Write the bumped version to `configurationDefinitions.yml`. Without this, the script just prints the recommendation. |
241+
242+
## Exit codes
243+
244+
### Generator exit codes (`generate-schema.py`)
245+
246+
| Code | Meaning |
247+
|------|---------|
248+
| 0 | No schema changes (or first run, or `--force` mode). |
249+
| 1 | Schema regenerated and on-disk differed (CI should commit). |
250+
| 2 | Generator failure (invalid schema, malformed inputs). |
251+
252+
### Bumper exit codes (`bump-schema-version.py`)
253+
254+
| Code | Meaning |
255+
|------|---------|
256+
| 0 | No bump needed (no schema diff, or bootstrap case where `<ref>` predates the schema). |
257+
| 1 | Bump applied (`--ci`) or recommended (without `--ci`). |
258+
| 2 | Bump failure (uncaught exception, missing args, malformed historical inputs). |
259+
260+
## Version bumping rules
261+
262+
`bump-schema-version.py` classifies each schema change and the bump
263+
kind is the highest severity across all changes:
264+
265+
| Change type | Severity | Bump |
266+
|-------------|----------|------|
267+
| Property removed | Breaking | Major |
268+
| Type changed | Breaking | Major |
269+
| Enum value removed | Breaking | Major |
270+
| Enum newly introduced | Breaking | Major |
271+
| Required field added | Breaking | Major |
272+
| `additionalProperties` tightened (true → false) | Breaking | Major |
273+
| Property added | Additive | Minor |
274+
| Enum value added | Additive | Minor |
275+
| Enum removed entirely | Additive | Minor |
276+
| Required field removed | Additive | Minor |
277+
| Default changed | Additive | Minor |
278+
| `additionalProperties` loosened (false → true) | Additive | Minor |
279+
| Description changed | Cosmetic | Patch |
280+
281+
## Running the tests
282+
283+
```bash
284+
# All schema-generation tests in one shot
285+
python3 -m unittest discover .fleetControl/schemaGeneration/tests
286+
287+
# Individual files
288+
python3 -m unittest .fleetControl.schemaGeneration.tests.test_generate_schema
289+
python3 -m unittest .fleetControl.schemaGeneration.tests.test_schema_diff
290+
python3 -m unittest .fleetControl.schemaGeneration.tests.test_bump_schema_version
291+
```

0 commit comments

Comments
 (0)