Просмотр исходного кода

Add Claude skills to add and removing config parameters

Jeremy Stretch 2 недель назад
Родитель
Сommit
9db8c207a2
2 измененных файлов с 385 добавлено и 0 удалено
  1. 217 0
      .claude/skills/add-config-param/SKILL.md
  2. 168 0
      .claude/skills/remove-config-param/SKILL.md

+ 217 - 0
.claude/skills/add-config-param/SKILL.md

@@ -0,0 +1,217 @@
+---
+name: add-config-param
+description: Step-by-step guide for adding a new configuration parameter to NetBox, covering both static parameters (settings.py) and dynamic parameters (database-backed, editable via the admin UI). Use when the user asks to add a new configuration option, setting, or parameter to NetBox.
+---
+
+# Adding a Configuration Parameter to NetBox
+
+NetBox has two distinct kinds of configuration parameters. Choose the right one before writing any code:
+
+| Type | Where defined | Changed by | Takes effect |
+|---|---|---|---|
+| **Static** | `settings.py` via `getattr(configuration, ...)` | Editing `configuration.py` + restart | On WSGI restart |
+| **Dynamic** | `config/parameters.py` `PARAMS` tuple | Admin UI or `configuration.py` | Immediately (cached in Redis) |
+
+**Use dynamic** when:
+- Operators need to tune the value without a service restart
+- The parameter controls UI behavior or defaults (banners, page sizes, default values)
+- Examples: `PAGINATE_COUNT`, `MAINTENANCE_MODE`, `BANNER_TOP`
+
+**Use static** when:
+- The value must not change at runtime (auth backends, database config, secret keys)
+- The value controls infrastructure that requires a restart anyway
+- Examples: `ALLOWED_HOSTS`, `REMOTE_AUTH_BACKEND`, `LOGGING`
+
+---
+
+## Adding a Dynamic Configuration Parameter
+
+Dynamic parameters are defined in `netbox/netbox/config/parameters.py`, stored in the `ConfigRevision.data` JSONField, cached in Redis, and editable via Admin > System > Configuration History.
+
+### Step 1 — Add to `PARAMS`
+
+**File:** `netbox/netbox/config/parameters.py`
+
+Add a `ConfigParam` entry to the `PARAMS` tuple, grouped logically with related parameters:
+
+```python
+ConfigParam(
+    name='MY_PARAM',
+    label=_('My param'),
+    default=<default_value>,
+    description=_("One-sentence description of what this controls"),
+    field=forms.BooleanField,   # or IntegerField, CharField, JSONField, SimpleArrayField
+    # field_kwargs only when extra widget/validation config is needed:
+    field_kwargs={
+        'widget': forms.Textarea(attrs={'class': 'vLargeTextField'}),
+    },
+),
+```
+
+**Common `field` choices:**
+
+| Field | Use for |
+|---|---|
+| `forms.CharField` (default) | Short strings |
+| `forms.BooleanField` | On/off toggles |
+| `forms.IntegerField` | Counts, sizes, timeouts |
+| `forms.JSONField` | Dicts/lists with free-form structure |
+| `SimpleArrayField` | Lists of strings (add `field_kwargs={'base_field': forms.CharField()}`) |
+
+The `default` value is returned whenever no `ConfigRevision` row exists and the parameter is not hard-coded in `configuration.py`.
+
+### Step 2 — Use the parameter in code
+
+Access via `get_config()` (request-scoped, cached) or the `ConfigItem` callable (deferred):
+
+```python
+from netbox.config import get_config
+
+# One-time read:
+value = get_config().MY_PARAM
+
+# Deferred (evaluated later):
+from netbox.config import ConfigItem
+MY_PARAM = ConfigItem('MY_PARAM')
+```
+
+`get_config()` returns the `Config` object which tries:
+1. Hard-coded value in Django `settings` (set by `configuration.py`)
+2. Redis-cached active `ConfigRevision`
+3. `ConfigParam.default`
+
+### Step 3 — Document in the configuration docs
+
+Add a section to the appropriate file under `docs/configuration/`:
+
+| File | Category |
+|---|---|
+| `miscellaneous.md` | General / doesn't fit elsewhere |
+| `default-values.md` | Default values for object fields |
+| `security.md` | Auth, permissions, URL validation |
+| `data-validation.md` | `CUSTOM_VALIDATORS`, `PROTECTION_RULES` |
+| `graphql-api.md` | GraphQL settings |
+| `error-reporting.md` | Sentry, logging |
+| `remote-authentication.md` | Remote auth settings |
+| `development.md` | Developer-only flags |
+| `system.md` | Low-level system settings |
+
+Template for a dynamic parameter doc section:
+
+```markdown
+## MY_PARAM
+
+!!! tip "Dynamic Configuration Parameter"
+
+Default: `<default_value>`
+
+One or two sentences describing what the parameter does, what values are accepted,
+and any side effects.
+```
+
+### Step 4 — Register in the dynamic params index
+
+**File:** `docs/configuration/index.md`
+
+Add the new parameter to the bulleted list under "Dynamic Configuration Parameters", keeping the list alphabetically ordered:
+
+```markdown
+* [`MY_PARAM`](./miscellaneous.md#my_param)
+```
+
+### Step 5 — Optionally add to the example config
+
+If the parameter is important enough that operators should know they can hard-code it, add a commented entry to `netbox/netbox/configuration_example.py`:
+
+```python
+# MY_PARAM = <default_value>
+```
+
+Place it near related parameters.
+
+### No migration needed
+
+Dynamic parameters are stored in the `ConfigRevision.data` JSONField, which already exists. No database migration is required when adding a new `ConfigParam`.
+
+---
+
+## Adding a Static Configuration Parameter
+
+Static parameters live in `settings.py` and are read at startup from `configuration.py`. They take effect only after the WSGI service is restarted.
+
+### Step 1 — Add to `settings.py`
+
+**File:** `netbox/netbox/settings.py`
+
+Add a line in the "Set static config parameters" block, alphabetically within its logical group:
+
+```python
+MY_PARAM = getattr(configuration, 'MY_PARAM', <default_value>)
+```
+
+For required parameters (no default), use `getattr(configuration, 'MY_PARAM')` with no fallback and add the parameter name to the required check near the top:
+
+```python
+for parameter in ('ALLOWED_HOSTS', 'MY_PARAM', 'SECRET_KEY', 'REDIS'):
+    if not hasattr(configuration, parameter):
+        raise ImproperlyConfigured(f"Required parameter {parameter} is missing from configuration.")
+```
+
+### Step 2 — Add validation (if needed)
+
+If the parameter has constrained values, add an `ImproperlyConfigured` check immediately after the `getattr` line:
+
+```python
+MY_PARAM = getattr(configuration, 'MY_PARAM', 'option_a')
+if MY_PARAM not in ('option_a', 'option_b'):
+    raise ImproperlyConfigured(f"MY_PARAM must be 'option_a' or 'option_b' (found {MY_PARAM})")
+```
+
+For complex validation (importable paths, valid URLs, etc.) follow the patterns of `PROXY_ROUTERS` or `RELEASE_CHECK_URL` in `settings.py`.
+
+### Step 3 — Add to the example config
+
+**File:** `netbox/netbox/configuration_example.py`
+
+Add a commented entry with a brief inline comment explaining the parameter:
+
+```python
+# MY_PARAM = 'default_value'    # Short description of what this does
+```
+
+### Step 4 — Document
+
+Add a section to the appropriate `docs/configuration/*.md` file:
+
+```markdown
+## MY_PARAM
+
+Default: `<default_value>`
+
+One or two sentences describing the parameter, accepted values, and any constraints.
+
+---
+```
+
+Static parameters do **not** get the `!!! tip "Dynamic Configuration Parameter"` admonition.
+
+---
+
+## Common Gotchas
+
+- **Dynamic params don't need a migration** — the value is stored in the `ConfigRevision.data` JSONField which already exists.
+- **Hard-coding a dynamic param in `configuration.py` overrides the UI** — the loop at the bottom of `settings.py` (`for param in CONFIG_PARAMS: ...`) sets the Django setting, which `Config.__getattr__` checks first. Document this behaviour in the parameter's doc page.
+- **`forms.BooleanField` with `required=False`**: the `ConfigFormMetaclass` always adds `required=False`, so a `BooleanField` correctly represents a three-state (True / False / unset-use-default) UI. No extra `field_kwargs` needed for booleans.
+- **`SimpleArrayField` needs `base_field`**: always pass `field_kwargs={'base_field': forms.CharField()}`.
+- **No `ruff format`** on existing files — use `ruff check` only.
+
+## References
+
+- Dynamic param definitions: `netbox/netbox/config/parameters.py`
+- Config loading / `Config` class: `netbox/netbox/config/__init__.py`
+- `ConfigRevision` model: `netbox/core/models/config.py`
+- `ConfigRevisionForm` (metaclass): `netbox/core/forms/model_forms.py`
+- Static config loading: `netbox/netbox/settings.py` lines 67–213
+- Example config: `netbox/netbox/configuration_example.py`
+- Config tests: `netbox/netbox/tests/test_config.py`
+- Documentation: `docs/configuration/`

+ 168 - 0
.claude/skills/remove-config-param/SKILL.md

@@ -0,0 +1,168 @@
+---
+name: remove-config-param
+description: Step-by-step guide for removing a configuration parameter from NetBox, covering both static parameters (settings.py) and dynamic parameters (database-backed). Use when the user asks to remove, delete, or deprecate a configuration option or setting.
+---
+
+# Removing a Configuration Parameter from NetBox
+
+Before touching any files, determine which type of parameter you are removing:
+
+| Type | Where defined | How to tell |
+|---|---|---|
+| **Static** | `settings.py` via `getattr(configuration, ...)` | Appears in `settings.py`; not in `config/parameters.py` `PARAMS` |
+| **Dynamic** | `config/parameters.py` `PARAMS` tuple | Appears in `PARAMS`; editable via Admin > System > Configuration History |
+
+Run a broad grep before starting to find all usages:
+
+```bash
+grep -r 'MY_PARAM' netbox/ --include='*.py' -l
+grep -r 'MY_PARAM' docs/ -l
+```
+
+---
+
+## Removing a Dynamic Configuration Parameter
+
+### Step 1 — Find all usages in code
+
+Before removing the parameter definition, identify every call site:
+
+```bash
+grep -r 'MY_PARAM\|my_param' netbox/ --include='*.py'
+```
+
+For `get_config().MY_PARAM` and `ConfigItem('MY_PARAM')` patterns specifically:
+
+```bash
+grep -r "get_config()\.MY_PARAM\|ConfigItem('MY_PARAM')" netbox/ --include='*.py'
+```
+
+Remove or replace every usage. The replacement depends on the reason for removal:
+- **Parameter folded into another**: replace with the new parameter access
+- **Hard-coded default**: replace `get_config().MY_PARAM` with the literal default value
+- **Feature removed**: remove the surrounding code entirely
+
+### Step 2 — Remove from `PARAMS`
+
+**File:** `netbox/netbox/config/parameters.py`
+
+Delete the `ConfigParam(...)` block for the parameter from the `PARAMS` tuple.
+
+### Step 3 — Remove from the dynamic params index
+
+**File:** `docs/configuration/index.md`
+
+Remove the bullet-point entry for `MY_PARAM` from the "Dynamic Configuration Parameters" list.
+
+### Step 4 — Remove the documentation section
+
+**File:** `docs/configuration/<category>.md` (whichever file the parameter was documented in)
+
+Delete the `## MY_PARAM` section and its content, including the trailing `---` separator.
+
+### Step 5 — Remove from the example config (if present)
+
+**File:** `netbox/netbox/configuration_example.py`
+
+If a commented `# MY_PARAM = ...` line was added when the parameter was introduced, remove it.
+
+### No migration needed
+
+Dynamic parameters are stored as keys in the `ConfigRevision.data` JSONField. Removing the `ConfigParam` definition from `PARAMS` means the UI no longer shows the field and the `Config` object no longer exposes the attribute — but old `ConfigRevision` rows in the database will silently retain the key in their JSON blob. This is harmless and requires no migration.
+
+---
+
+## Removing a Static Configuration Parameter
+
+### Step 1 — Find all usages in code
+
+```bash
+grep -r 'MY_PARAM' netbox/ --include='*.py'
+```
+
+Remove every reference. For Django settings accessed via `settings.MY_PARAM`, also search templates:
+
+```bash
+grep -r 'MY_PARAM' netbox/templates/
+```
+
+### Step 2 — Remove from `settings.py`
+
+**File:** `netbox/netbox/settings.py`
+
+1. Delete the `MY_PARAM = getattr(configuration, 'MY_PARAM', ...)` line.
+2. If the parameter was required (listed in the required-parameter check near the top), remove it from that tuple:
+   ```python
+   # Before:
+   for parameter in ('ALLOWED_HOSTS', 'MY_PARAM', 'SECRET_KEY', 'REDIS'):
+   # After:
+   for parameter in ('ALLOWED_HOSTS', 'SECRET_KEY', 'REDIS'):
+   ```
+3. Remove any validation block that immediately followed the `getattr` line (e.g. `if MY_PARAM not in (...): raise ImproperlyConfigured(...)`).
+
+### Step 3 — Remove from the example config
+
+**File:** `netbox/netbox/configuration_example.py`
+
+Delete the commented `# MY_PARAM = ...` line.
+
+### Step 4 — Remove the documentation section
+
+**File:** `docs/configuration/<category>.md`
+
+Delete the `## MY_PARAM` section and its content, including the trailing `---` separator.
+
+---
+
+## Deprecation vs. Immediate Removal
+
+If the parameter is used by existing deployments, consider a two-phase removal:
+
+**Phase 1 (current release) — Deprecate:**
+1. Keep the `getattr` / `ConfigParam` definition in place so existing configs don't break.
+2. Add a deprecation warning comment in `settings.py` (see how `SENTRY_DSN` is handled with `# TODO: Remove in NetBox vX.Y`).
+3. Log a `warnings.warn(...)` or add a startup notice if the parameter is still set.
+4. Mark the doc section as deprecated.
+
+**Phase 2 (future release) — Remove:**
+Follow the full removal steps above.
+
+---
+
+## Common Gotchas
+
+- **Remove all call sites first** — if code still calls `get_config().MY_PARAM` or `settings.MY_PARAM` after the definition is gone, startup or runtime will raise `AttributeError`.
+- **Old ConfigRevision rows keep the key** — this is safe; the `Config.__getattr__` lookup simply won't find a registered default and will raise `AttributeError` if code tries to access it. Remove all code references before removing the `ConfigParam`.
+- **`configuration.py` in user deployments** — removing a static parameter may cause a `TypeError` or silent failure if users have `MY_PARAM = ...` in their local `configuration.py`. Document the removal in the release notes.
+- **No `ruff format`** on existing files — use `ruff check` only.
+
+## Summary Checklist
+
+### Dynamic parameter
+
+| # | File(s) | Action |
+|---|---|---|
+| 1 | All `.py` files | Remove all `get_config().MY_PARAM` and `ConfigItem('MY_PARAM')` usages |
+| 2 | `netbox/netbox/config/parameters.py` | Remove `ConfigParam(...)` block from `PARAMS` |
+| 3 | `docs/configuration/index.md` | Remove bullet-point entry |
+| 4 | `docs/configuration/<category>.md` | Remove `## MY_PARAM` section |
+| 5 | `netbox/netbox/configuration_example.py` | Remove commented entry (if present) |
+
+### Static parameter
+
+| # | File(s) | Action |
+|---|---|---|
+| 1 | All `.py` and template files | Remove all `settings.MY_PARAM` / `MY_PARAM` usages |
+| 2 | `netbox/netbox/settings.py` | Remove `getattr` line; remove from required-params tuple; remove validation block |
+| 3 | `netbox/netbox/configuration_example.py` | Remove commented entry |
+| 4 | `docs/configuration/<category>.md` | Remove `## MY_PARAM` section |
+
+## References
+
+- Dynamic param definitions: `netbox/netbox/config/parameters.py`
+- Config loading / `Config` class: `netbox/netbox/config/__init__.py`
+- `ConfigRevision` model: `netbox/core/models/config.py`
+- Static config loading: `netbox/netbox/settings.py` lines 67–213
+- Example config: `netbox/netbox/configuration_example.py`
+- Documentation: `docs/configuration/`
+- `add-config-param` skill: `.claude/skills/add-config-param/SKILL.md` (reverse of this skill)