Extending the App¶
Common extensions, ordered roughly by how much code each requires.
Adding an unmapped IP protocol number¶
If you see a service skipped with the log message
"protocol-number N not mapped", extend
IP_PROTOCOL_NUMBER_TO_NAME in src/nautobot_ssot_fortinet/utils/fortios.py:
The inverse table IP_PROTOCOL_NAME_TO_NUMBER rebuilds from this automatically. The new name must exist in nautobot_firewall_models.choices.IP_PROTOCOL_CHOICES — if not, you'll get a ValidationError: not a valid choice on push.
Adding a FortiOS security mode → Nautobot auth choice¶
Extend FORTIOS_VAP_SECURITY_MAP in utils/fortios.py:
FORTIOS_VAP_SECURITY_MAP: dict[str, str] = {
...,
"your-new-security-string": "Nautobot Auth Choice",
}
The Nautobot side string must exactly match a value in nautobot.wireless.choices.WirelessNetworkAuthenticationChoices.
Adding a new pull endpoint¶
Example: sync cmdb/firewall/schedule (firewall schedules).
- Add a DiffSync model class in
diffsync/models/firewall.py:
class Schedule(DiffSyncModel):
_modelname = "schedule"
_identifiers = ("name",)
_attributes = ("start", "end", "hostname", "vdom")
name: str
start: str
end: str
hostname: str
vdom: str
-
Add a Nautobot-side subclass in
diffsync/models/nautobot_firewall.pywithcreate/update/deleteagainst whatever model you're mapping to (here, you'd need to either add a custom field on existing AddressObject, or model it as a Tag, or skip persisting and just log). -
Register the model on both adapters (
fortigate_firewall.pyandnautobot_firewall.py):
-
Implement
_load_schedules()on each adapter — FortiGate side callsself.client.cmdb.firewall.schedule.get(); Nautobot side queries the ORM with the appropriate scope. -
Add a fixture in
tests/fixtures/firewall_schedule.jsonand corresponding tests.
Adding push support for a new object kind¶
The push direction requires three pieces:
- A new model subclass in
diffsync/models/fortigate_target_firewall.pywithcreate/update/deletethat build FortiOS payloads and calladapter.client.cmdb.firewall.<endpoint>.create/update/delete. - Register the subclass on
FortiGateFirewallTargetAdapterindiffsync/adapters/fortigate_firewall_target.py. - Inverse translation helpers if any push-direction transform doesn't already have one. See the table in architecture.md.
Pattern reference: see how FortiGateAddressObject and FortiGateServiceObject are implemented.
Adding a new live-status section¶
Extend FortiGateLiveStatus.run() in jobs.py to query additional monitor/* endpoints. The existing _safe_get helper handles flaky endpoints (returns [] on error rather than failing the whole Job).
sessions = _safe_get(fgt, "api/v2/monitor/firewall/session?count=100")
# ... render to log + include in snapshot dict
Adding wireless push direction¶
Wireless push isn't implemented yet but the scaffolding pattern is clear:
- Add write-enabled subclasses in a new file
diffsync/models/fortigate_target_wireless.py(mirroringfortigate_target_firewall.py). - Add a new adapter
diffsync/adapters/fortigate_wireless_target.pyinheriting fromFortiGateWirelessAdapter. - Add a new Job
FortiGateWirelessDataTargetinjobs.py.
For wireless specifically, the FortiGate REST shape for vap write differs from read in subtle ways (e.g., passphrase is write-only). Test carefully with dry_run=True first.
Testing your extension¶
- Unit tests under
tests/— match the existing patterns. For pure helpers, write tests intest_utils_fortios.py. For adapter behavior, followtest_adapters_*.py. - Fixture-based e2e — add a fixture under
tests/fixtures/and reference it in adevelopment/scripts/e2e_*.pyscript. - Live e2e — run against a real FortiGate via
make -C development e2e-live-*. Always test with dry-run first if your change touches the push direction.
See also¶
- Architecture — the conceptual model
- Contributing — how to submit changes back