forward-netbox 2.7.2__tar.gz → 2.7.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/PKG-INFO +6 -5
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/README.md +4 -3
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/__init__.py +1 -1
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/exceptions.py +8 -0
- forward_netbox-2.7.4/forward_netbox/management/commands/forward_interface_vlan_audit.py +30 -0
- forward_netbox-2.7.4/forward_netbox/migrations/0050_contributor_baseline_cascade.py +49 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/models.py +7 -1
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/signals.py +53 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_dlm_integration.py +48 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_ingestion_delete.py +82 -22
- forward_netbox-2.7.4/forward_netbox/tests/test_interface_vlan_audit.py +148 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_merge_rule_rejection.py +183 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_optional_plugin_versions.py +2 -1
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_plugin_integrations.py +5 -5
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_protecting_relations.py +22 -4
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_runtime_dependency_check.py +1 -1
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_sync.py +150 -1
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/apply_engine_bulk.py +97 -10
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/apply_engine_decision.py +1 -1
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/bulk_merge.py +25 -4
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/diagnostics.py +93 -9
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/fast_baseline.py +2 -2
- forward_netbox-2.7.4/forward_netbox/utilities/interface_vlan_audit.py +98 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/merge.py +18 -1
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/merge_set_based.py +1 -1
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/plugin_integrations/registry.py +2 -2
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_dlm.py +7 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_primitives.py +42 -1
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_reporting.py +68 -14
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/views.py +46 -14
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/pyproject.toml +2 -2
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/LICENSE +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/api/__init__.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/api/serializers.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/api/urls.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/api/views.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/choices.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/filtersets.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/forms.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/jobs.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/__init__.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/__init__.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_accept_merge_failures.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_apic_cimc_readiness_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_apply_identity_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_blocker_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_collection_gap_alert.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_device_scope_reconciliation_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_fast_baseline_preflight.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_link_vsys_parents.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_metrics.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_module_readiness.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_ownership_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_primary_ip_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_profile_merge.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_pushdown_profile.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_query_diff_coverage_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_resolve_query_ids.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_routing_dangling_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_scope_ipam_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_seed_ui_harness.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_smoke_sync.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_stuck_job_alert.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_stuck_job_recover.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_tag_delete_eligible.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_validation_org_query_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_warning_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/management/commands/forward_watch_sync.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0001_initial.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0002_add_coalesce_fields.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0003_forwardingestionissue_context_fields.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0004_forwardingestion_baseline_fields.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0005_validation_and_reporting.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0006_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0007_forwardingestion_persisted_change_counters.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0008_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0009_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0010_forwardvalidationrun_override_applied_and_more.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0011_forwardingestion_change_request_id.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0012_forwardnqemap_query_reference.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0013_execution_run_step.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0014_execution_step_fetch_scope.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0015_execution_step_apply_engine.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0016_execution_step_query_metrics.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0017_execution_step_row_counters.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0018_execution_run_reconciliation_events.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0019_forwardexecutionstep_query_parameters.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0020_execution_step_operation.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0021_add_fhrpgroup_nqe_map_choice.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0022_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0023_split_ip_address_maps.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0024_forwarddeviceanalysis.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0025_device_analysis_netboxmodel.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0026_forwarddeviceanalysis_collection_result.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0027_alter_forwardexecutionrun_backend.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0028_remove_forwardexecutionstep_run_and_more.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0029_forward_parent_device_cf.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0030_forwarddeviceanalysis_cve_ids.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0031_encrypt_source_password.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0032_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0033_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0034_source_ownership_provenance.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0035_initialize_ownership_control_plane.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0036_protect_ownership_provenance.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0037_merge_identity_and_assignment_provenance.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0038_managed_tag_domain_guard.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0039_forwardingestion_catchup_state.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0040_remove_redundant_nqe_settings.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0041_forwardworkloadstate.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0042_device_generic_relation_guards.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0043_forwardnqemap_diff_contract.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0044_contributor_baseline_cache.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0045_forwardmergeattempt.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0046_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0047_ownership_reconciliation_cascade.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0048_forwardingestion_skipped_change_count.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/0049_drift_policy_row_shrink_floor.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/migrations/__init__.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/navigation.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/__init__.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_apic_cimc_inventory.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_apic_nodes.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_bridge_domains.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_command_inventory.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_fabrics.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_filters.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_l3outs.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_nodes.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_pods.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_tenants.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_aci_vrfs.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_bgp_address_families.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_bgp_peer_address_families.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_bgp_peers.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_cimc_endpoint_inventory.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_device_analysis.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_device_feature_tags.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_device_feature_tags_with_rules.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_device_models.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_device_models_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_device_types.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_device_vendors.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_devices.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_devices_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_dlm_cves.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_dlm_device_software.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_dlm_hardware_notices.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_dlm_hardware_notices_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_dlm_inventory_item_software.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_dlm_software_versions.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_dlm_vulnerabilities.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_hsrp_groups.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_inferred_interface_cables.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_interfaces.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_inventory_items.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_ip_addresses_ipv4.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_ip_addresses_ipv6.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_ip_addresses_unassignable_diagnostics.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_locations.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_mac_addresses.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_modules.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_ospf_areas.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_ospf_instances.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_ospf_interfaces.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_peering_sessions.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_platforms.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_prefixes_ipv4.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_prefixes_ipv6.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_routing_import_diagnostics.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_virtual_chassis.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_vlans.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/forward_vrfs.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/queries/netbox_utilities.nqe +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/static/forward_netbox/img/fn-logo.svg +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tables.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/template_content.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/device_cves.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwarddriftpolicy.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardingestion.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardingestionissue.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardnqemap.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardsource.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardsync.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardsync_dependency_preview.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardsync_drift_report.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardsync_health.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardsync_module_readiness.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardsync_scope_reconciliation.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/forwardvalidationrun.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/inc/brand_bar.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/inc/device_analysis_panel.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/inc/diff.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/inc/execution_insights.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/inc/logs_pending.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/inc/merge_form.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/inc/validation_force_allow_form.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/partials/change_explainability.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/partials/ingestion_all.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/partials/ingestion_merge_button.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/partials/ingestion_progress.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/partials/ingestion_statistics.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/partials/ingestion_status.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/partials/job_logs.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/partials/object_tabs.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/partials/stage_switcher_buttons.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templates/forward_netbox/partials/validation_force_allow_button.html +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templatetags/__init__.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/templatetags/forward_netbox_helpers.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/__init__.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/fixtures/aci_command_inventory_expected.json +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/fixtures/aci_discovery_expected.json +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_accepted_merge_failures.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_api_usage.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_api_views.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_apic_cimc_readiness_audit_command.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_apply_engine.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_apply_identity_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_bgp_peer_comment_churn.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_branch_budget_split.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_branching.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_brand_bar.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_builtin_query_id_binding.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_bulk_adapter_parity.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_bulk_merge.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_bulk_merge_scale.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_button_jobs.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_contract_preflight_message.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_contributor_baseline.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_copy_sql_apply_engine.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_crypto.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_density_observation.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_dependency_preview_summary.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_device_analysis_query.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_device_scope_reconciliation_audit_command.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_device_scope_tagging.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_dlm_catalogue_conflicts.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_empty_workload_failure.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_endpoints_import.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_execute_by_query_id_without_commit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_execution_insights_delete_label.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_failed_run_model_evidence.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_fast_baseline.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_field_reported_integrity_failures.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_forms.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_forward_api.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_head_commit_from_listing.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_health.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_ingestion_merge.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_interface_naming.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_ip_address_dedup_query.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_ipaddress_dup_global.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_issue_diagnosis.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_issue_rendering.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_job_liveness.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_job_queue.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_jobs.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_license_tier.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_log_export.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_logging.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_merge_observability.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_merge_profiling.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_migrations.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_model_view_routes.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_models.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_module_readiness.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_module_readiness_rollup.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_operator_commands.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_ownership.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_ownership_command.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_ownership_conflict_reason.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_ownership_migration.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_post_sync.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_primary_ip.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_primary_ip_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_primary_ip_integration.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_protecting_reference_deletes.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_provision_preflight.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_query_binding.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_query_diff_coverage_audit_command.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_query_execution_contract.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_query_id_binding_end_to_end.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_query_registry.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_query_spec_head_resolution.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_query_variants.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_release_readiness_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_row_count_floor.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_scheduled_jobs.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_scope_ipam_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_scope_matched_tags.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_scope_module_ui.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_scope_reconciliation_view.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_set_based_merge.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_smoke_sync_command.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_stability_hardening.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_stuck_recovery.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_support_bundle_archive.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_sync_aci.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_sync_facade.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_sync_orchestration.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_sync_recurrence_pinning.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_sync_runner_contracts.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_sync_state.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_tier3_reducers.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_upgrade_reconciliation.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_validation_org_query_audit_command.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_vsys_parent.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_workload_normalization.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/tests/test_workload_state.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/urls.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/__init__.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/api_usage.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/apply_engine.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/apply_engine_copy_sql.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/apply_identity_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/branch_budget.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/branch_lifecycle.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/branching.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/bulk_delete.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/change_explainability.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/contributor_baseline.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/crypto.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/delete_policy.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/density_learning.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/device_analysis.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/direct_changes.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/drift_report.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/execution_telemetry.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/executor_base.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/fast_baseline_models.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/forward_api.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/forward_api_impl.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/health.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/health_apply_fetch.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/health_checks.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/health_summary_blocks.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/ingestion_issues.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/ingestion_merge.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/ingestion_presentation.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/interface_naming.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/job_liveness.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/job_queue.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/json_safe.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/license_tier.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/logging.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/merge_observability.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/merge_profiling.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/model_contracts.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/model_validation.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/module_readiness.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/ownership.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/plugin_integrations/__init__.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/post_sync.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/primary_ip.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/primary_ip_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/query_binding.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/query_binding_resolution.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/query_diagnostics.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/query_execution_contract.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/query_fetch.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/query_fetch_execution.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/query_registry.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/runtime_guidance.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/scope_ipam_audit.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/scope_reconciliation.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/single_branch_executor.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/snapshot_freshness.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/stuck_recovery.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/support_bundle_archive.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_aci.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_cable.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_contracts.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_core_models.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_device.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_events.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_execution.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_facade.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_interface.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_inventory_module.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_ipam.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_orchestration.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_routing.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_routing_impl.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_runner_adapters.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_runner_contracts.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/sync_state.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/tag_contracts.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/tier3_reducers.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/upgrade_reconciliation.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/validation.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/validation_org_query.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/version_series.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/vsys_parent.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/workload_normalization.py +0 -0
- {forward_netbox-2.7.2 → forward_netbox-2.7.4}/forward_netbox/utilities/workload_state.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forward-netbox
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.4
|
|
4
4
|
Summary: NetBox plugin to sync Forward data into NetBox via built-in NQE queries
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -28,7 +28,7 @@ Provides-Extra: peering
|
|
|
28
28
|
Provides-Extra: routing
|
|
29
29
|
Requires-Dist: httpx (>0.26,<0.29)
|
|
30
30
|
Requires-Dist: netbox-cisco-aci (==0.4.0) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "aci" or extra == "integrations")
|
|
31
|
-
Requires-Dist: netbox-dlm (>=0.4.1,<0.
|
|
31
|
+
Requires-Dist: netbox-dlm (>=0.4.1,<0.8.0) ; extra == "dlm" or extra == "integrations"
|
|
32
32
|
Requires-Dist: netbox-peering-manager (==0.3.0) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "peering" or extra == "integrations")
|
|
33
33
|
Requires-Dist: netbox-routing (==0.4.3) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "routing" or extra == "integrations")
|
|
34
34
|
Requires-Dist: netboxlabs-netbox-branching (>=1.1.1,<1.2.0)
|
|
@@ -106,15 +106,16 @@ See the
|
|
|
106
106
|
|
|
107
107
|
## Release Compatibility
|
|
108
108
|
|
|
109
|
-
The `2.7.
|
|
109
|
+
The `2.7.4` release candidate requires NetBox `4.6.x` (>= `4.6.5`, tested on `4.6.6`) and `netbox-branching` `1.1.x` (tested on `1.1.2`). Expand for the published release history and candidate notes.
|
|
110
110
|
|
|
111
111
|
<details>
|
|
112
112
|
<summary>Release compatibility history</summary>
|
|
113
113
|
|
|
114
114
|
| Plugin Release | NetBox Version | Status |
|
|
115
115
|
| --- | --- | --- |
|
|
116
|
-
| `v2.7.
|
|
117
|
-
| `v2.7.
|
|
116
|
+
| `v2.7.4` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Release candidate. Published as `2.7.4`: `2.7.3` was tagged but never published, so no `2.7.3` artifact exists on PyPI - the release commit recorded no `## Release Authorization` evidence, and the publish workflow refuses a tag whose plan cannot show which gates ran against that exact tree, so it stopped before building anything. 2.7.4 is the same tree with that evidence recorded. Fix: **one interface NetBox refuses no longer fails the whole sync**. A `ValidationError` raised while validating an interface left the apply engine entirely and surfaced as a terminating failure that named no model and no row, so a run that had staged 172 changes ended having applied none and the only evidence produced identified neither the device nor the interface. The row is now recorded against `dcim.interface` and the rest of the shard proceeds. Fix: **a rejection now says which rule rejected it, not just which field**. The rule catalogue read only the errors NetBox declines to attach to a field, which made a rule legible exactly when NetBox would not name a field and illegible whenever it did. `untagged_vlan` alone carries two unrelated NetBox rules - an interface whose mode admits no untagged VLAN, and a VLAN outside the device's site - with different causes and different fixes, and no evidence the plugin produced could tell them apart. Every field is read now, both rules are catalogued, and the field and the rule are reported together. Fix: **a rejection about state the sync did not write is skipped rather than failed**. Validation covers the whole object while an update writes only the fields that differ, so an untagged VLAN left behind by a device that moved sites fails a later sync that changes only the MTU. Refusing to write is right; calling it a failure is not, because a failure also fails the row's dependents and no retry can change the answer. A catalogued rule on a field the row does not write is now recorded and skipped. A rule the catalogue cannot name stays a failure, deliberately. Fix: **both apply paths reach the same verdict**. The bulk engine and the row-oriented adapter had come to opposite conclusions about the same interface while each was locally correct, and the parity harness could not see it because both write nothing in that case. One shared rule now answers it for both. Fix: **the merge stops filing its own defects as unfixable**. Every validation rejection at merge counted as unsatisfiable, which is right for a rule that belongs to the destination and wrong for a rejection the merge itself caused, where retrying is the correct response. Only a catalogued rule landing on a field the change writes moves; an uncatalogued rule still skips, so nothing can newly block a baseline. Fix: **a spent baseline is no longer called the current one**. Every ingestion that promotes leaves a baseline record behind, and promotion only marks the previous one superseded - it clears the contents but keeps the row, which goes on protecting its ingestion. The refusal told an operator with three undeletable ingestions that each was the baseline for the sync, true of at most one, and pointed at a repair the product does not offer. It now says the record is spent, that it still protects the ingestion, and that nothing releases it today. Collecting spent baselines is still a known gap. Feature: **a command that finds the interfaces NetBox will refuse**. `forward_interface_vlan_audit` reports every interface whose untagged VLAN belongs to another site, and every interface carrying one with no 802.1Q mode, naming the device, the interface, both sites and the VLAN ID. Persisted diagnostics carry schema identifiers and never collected values, which is why a rejected row cannot name itself; this computes the answer on demand and writes it to the operator's console instead. Fix: **an ingestion whose baseline has been superseded can be deleted**. Every ingestion that promotes leaves a baseline record behind, and promotion only marks the previous one superseded - it clears the contents but keeps the row, which goes on protecting its ingestion. Nothing ever removed one, so every ingestion that had promoted was permanently undeletable and the backlog grew by one per successful sync. A spent record now goes with its ingestion; the live one is still kept, by every route rather than only through the delete button. Fix: **an ingestion whose job is still running is refused**, the same protection a sync already had. Deleting one mid-flight removed the job record while its worker carried on against it. Fix: **a skipped row says which model it was waiting for**, and says skipped rather than failed. Six identical rows naming only an exception class told an operator nothing about which parent was missing. Rows skipped while pruning are the inverse case and now name the records still referencing the object, which is why the prune was refused. Compatibility: **netbox-dlm `0.7.0` is accepted**, validated against the full suite rather than accepted on its changelog. No migration and no operator step; upgrade and re-run the sync. |
|
|
117
|
+
| `v2.7.2` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Current release; Fix: **a query bound by ID no longer has to prove a commit before it can run**. A map bound to a Forward query by direct ID with no stored commit was rejected before execution, and because one ineligible map empties the whole plan, nothing was scheduled and the sync ended having fetched nothing - while every Forward call returned 200. The plugin was resolving a commit on Forward's behalf by walking the query history for one whose source matched its own bundled copy; Forward never required it, and the execution payload already omitted it for head. A query ID now executes at Forward's latest. An explicitly stored commit is still honoured, and diff execution is unchanged because there the two commits are part of the request path. A query whose parameters have moved on now fails on its own model, naming the map, instead of silently emptying the run. Fix: **a run whose model comes back far smaller than its baseline is refused**. Dropping the commit check removed the comparison that used to notice a query changing underneath a map, which left one case invisible: a query that still accepts the same parameters and still returns the same shape, but fewer rows. Those rows are authoritative, so the absent ones reconcile as deletions with nothing reported. A model returning more than 30% fewer rows than its last promoted baseline, and at least 20 fewer, now blocks the run before anything is staged. It is on by default and a sync with no drift policy is still covered; a policy can widen or disable it, the absence of one cannot. A first run, a diff run, a model already failing, and a scope the operator narrowed are all excluded. Fix: **a failed sync now says why, per model**. The reason was reduced to an exception class name before anything recorded it, so no log, database row, or support bundle could recover it - one deployment ran five consecutive failed syncs that could not be diagnosed from any evidence it produced. Failures now carry a catalogued reason alongside the class, resolved once in a shared place rather than as another per-case exception. Two paths that composed their own message, and two job outcomes that were exported as redacted because they were phrased differently, were folded into it. Messages that embed collected values are still reduced to wording and slugs. Release flow: **a post-release commit that carries executable content fails the harness**. The first commit after a release tag must be documentation only, and it cannot be reclaimed once taken, so getting it wrong makes every later release unverifiable. No migration beyond the drift-policy fields, which apply themselves, and no operator step; upgrade and re-run the sync. |
|
|
118
|
+
| `v2.7.1` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.7.2`; Fix: **a merge delete blocked by a hidden PROTECT reference is now predicted instead of discovered at apply time**. 2.7.0 shipped this as a known limitation because widening the predictor on reasoning alone kept a device that should have been deleted; the predictor now accounts for the provenance rows the merge clears inside the same transaction, so a delete is scheduled when nothing outside the merge still protects the row and is still refused when something does. Fix: **a CVE and the software version referencing it no longer fail when both are created in the same merge**. The dependency graph modelled scalar foreign keys but not many-to-many references, so the target could be ordered after the row pointing at it. Latent since 2.5.11 and only reachable once the CVE maps were enabled. Fix: **deleting an ingestion now takes its own scope-reconciliation rows with it**. Those rows protect the ingestion, so an ingestion that had reconciled could never be removed and the refusal named a model the operator has no way to clear. Fix: **the ingestion issues list no longer returns a 500**. The view was registered but its URL was never included, so the list route resolved to nothing while the detail route worked. A test now sweeps every model with a detail route for the same omission. Fix: **a row NetBox can never accept is now recorded as skipped rather than failed**. Any failed row blocks baseline promotion, and without promotion there is no bookkeeping and drift reports Not measured, so a handful of permanently unsatisfiable addresses could mask thousands of correct changes. Integrity and unrecognised errors still fail and still block. Security: takes the `cryptography` fix for CVE-2026-69247. Release flow: dependency advisories are audited before the gate rather than after the tag, a skipped post-release step now fails the harness instead of passing quietly, and a release opens its own close-out work. No migration and no operator step; upgrade and re-run the sync. |
|
|
118
119
|
| `v2.7.0` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.7.1`; Fix: **every ingestion was undeletable, and the refusal explained nothing**. `ForwardIngestionProvenanceMixin` declares its ingestion foreign key as PROTECT with `related_name="+"`, and Django hides such relations from `_meta.related_objects` - which is exactly what the protection check iterated, so of the five PROTECT relations pointing at an ingestion it could see one. An ingestion held only by device identities reported no refusal, so the delete view fell through to NetBox's confirmation page, rendered one dependent row per synced device, and then failed with `ProtectedError` on confirm. Every ingestion owns one identity per device, which is why the count grew with every sync rather than staying at one. Fix: **a primary IP moving between devices now converges**. The ownership proof was read through the active branch, and a branch is a snapshot of the moment it was provisioned, so an identity finalized after that provision was invisible and read as "not ours" - suppressing a release we were entitled to make. The holder lookup and the writes stay branch-native; only the ownership proof is pinned to the control plane. Feature: **CIMC firmware is reported into device lifecycle**. netbox-dlm `0.6.0` adds `InventoryItemSoftware`, so the firmware running on each Cisco management controller now lands in DLM alongside every device's OS version. The version is read from the SNMP sysDescr Forward already collects, so the map requests no additional collection; an endpoint whose sysDescr carries no firmware clause yields no row rather than a guessed one. Opt-in, and it attaches to the inventory items the CIMC endpoint map creates. Fix: **an optional plugin version that did not match exactly silently disabled its whole integration**. `required_package_version` was an equality gate, and an unmatched version skips every model of that integration; it now expresses a set of supported versions, the same shape the fast baseline already uses for the same reason. Known limitation: a merge delete blocked by one of those hidden PROTECT relations is still not predicted, so it is scheduled and fails at apply time, and a failed row blocks baseline promotion. Widening the merge predictor was tried and measured: it silently kept a device that should have been deleted, because the merge clears that reference in the same transaction. Deferred rather than shipped on reasoning. No migration and no operator step; upgrade and re-run the sync. |
|
|
119
120
|
| `v2.6.12` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.7.0`; Published as `2.6.12`: `2.6.10` and `2.6.11` were tagged but never published, so no artifact exists for either and PyPI went from `2.6.9` to this release. 2.6.12 carries both tranches plus the release-path fix below. Fix: **a merge failure that names no field now names its rule**. A non-field validation error reports `__all__` — a field name meaning "no field" — so a customer's `ipam.ipaddress` merge failure was indistinguishable from any other cause. NetBox interpolates the message before raising, putting the address inside the string, so it cannot be persisted as-is: known rules resolve to slugs this plugin defines, and anything uncatalogued is reduced to wording with every value-bearing token masked. Confirmed in the field: a failure that had read only `ValidationError` now reads `violating primary-ip-reassignment-blocked`. Fix: **a row NetBox refuses on its own validation rule no longer wedges the baseline**. Any failed row blocks baseline promotion, and re-running cannot change a validation rejection because the rule belongs to the destination rather than the incoming data, so a few permanently unsatisfiable addresses could stop thousands of correct changes from ever being attested. Those rows are recorded and skipped; integrity and other errors still fail and still block. Fix: **the ingestion log download contains the issues behind the run** — message, exception and structured diagnosis, with an explicit truncation flag past 200 rows. The exception text was previously discarded before anything persisted it, so searching server logs for it could not have matched on any deployment. Fix: **the one ingestion that cannot be deleted no longer reads as an error** — it is the baseline, the durable record of what has already converged, and NetBox protects it deliberately; the refusal now says so and is raised as a warning. Fix: **the scope reconciliation refresh button no longer returns a 500 while also queueing a job that never runs**. The view handed the job class to the queue where a callable was required; RQ rejects a class, but only at dispatch, which happens on transaction commit — after the Job row is already written. Fix: **a failed ownership reconciliation names which conflict refused it**. Eight sites raised the error bare and the message was stripped before the job record was written, so the customer-visible failure carried the exception class and nothing else. The messages embed device names, so an allowlist maps each known conflict to a slug, and an unmatched message is recorded as unrecognised rather than falling silent. Release flow: **the release provenance gate is now satisfiable**. Two of its requirements had not been jointly satisfiable since the 2.6 series began: every commit in the reviewed range must still have a successful main-push run, which GitHub expires, while the security-bootstrap check required the three scanner files to appear in a diff from the anchor — true only while the anchor sat immediately before the single commit that introduced them. A fixed anchor expires runs and a moved anchor empties that diff, which is why `2.6.10` and then `2.6.11` both failed at the tag. The scanner is now verified at the release commit against pinned digests, which is strictly stronger than the diff test it replaces because that test never looked at content, and which frees the anchor to advance. No migration and no operator step; upgrade and re-run the sync. |
|
|
120
121
|
| `v2.6.9` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.6.12`; Published as `2.6.9`: `2.6.7` and `2.6.8` were tagged but never published, so no artifact exists for either — a release-time gate resolved the version to upgrade from by reading git tags, and a tag records only what was tagged, not what reached the index. 2.6.9 carries both tranches plus the fixes below. Fix: **a merge failure names the constraint or field it violated** in the issue list, not only in the record behind it — the detail had been stored since 2.6.6 but the list still showed just the exception class, so a merge `ValidationError` looked identical whatever caused it. Fix: **the one ingestion that cannot be deleted says so first**. Deleting the baseline ingestion rendered every dependent object — one per synced device — and only then refused, so the refusal arrived after several hundred device names. Release flow: the upgrade gate moved out of the tag-triggered publish into pull-request CI, where a failure blocks a merge rather than consuming a version number that cannot be reused, and the version to upgrade from is resolved from the package index rather than from tags. Fix: **a module whose type re-creates a component another module already owns** no longer fails the row. NetBox adopts an existing component only when it belongs to no module, so a console port already claimed by a different module can be neither adopted nor recreated and the database rejects the duplicate name; because a failed row blocks baseline promotion, one such module stalled a whole sync. The collision is now detected before the write and reported as a rolled-up skip. Fix: **two devices resolving the same management address** no longer fail the ingestion. NetBox allows an address to be the primary IP of exactly one device, and the `Mgmt_`-tag path assigned it without checking, so a shared management address ended the sync at `dcim_device_primary_ip4_id_key` after every workload had already been staged. Each address is claimed once and the conflicts are reported. Release flow: the build job now checks out with tags, and a harness check fails closed if a job running a tag-dependent task ever loses them again. Convergence, diagnosability and apply-performance release. Fix: **a device could fail to adopt its primary IP** with `The specified IP address is not assigned to this device` — when the device and the address both already existed and a sync only re-pointed them, nothing ordered the two updates and whichever change was recorded first won. A race, which is why it presented as a single device out of thousands and never reproduced. Fix: **a delete the database will always refuse no longer fails the row** — a delete held by a PROTECT reference that survives the run was still attempted, and because any failed row blocks baseline promotion, one operator-owned object could wedge a sync's convergence bookkeeping indefinitely. It is now reported and skipped, leaving the intent visible in drift. Fix: **a failed sync explains itself in the UI** — a terminating failure recorded no model and no detail, so the one issue that says why a run died was the least informative thing on the page and the constraint was recoverable only from server logs. Constraint, table and invalid-field names are now recorded on every sync failure, in the issue message and over the API; the key values behind them still are not. Fix: **one invalid MAC row no longer aborts the whole batch**, and two stored rows sharing a canonical address are reported instead of one being overwritten at random. Feature: **an opt-in COPY/SQL apply engine for MAC addresses**, off by default and version-pinned, measured 8.98x faster on a realistic mix with full fallback to the existing engine. Performance: **IP address creation issues a third fewer queries** by not re-fetching an interface and device that validation never needed. Feature: **a license-tier refusal from Forward is named as one** — organization-authored queries require a NETWORK-facet tier and CVE data additionally requires SECURITY, so the failure says which capability is missing instead of surfacing a raw HTTP error. Fix: **an optional plugin can be upgraded again** — `netbox-dlm` was pinned to an exact `0.4.1`, so `0.5.0` was refused at install; worse, the fast baseline, set-based merge and COPY/SQL engines each compared the whole optional-plugin tuple exactly, so getting past the install pin would have switched all three off with nothing an operator could see — turning a first sync from minutes into hours. Each engine now lists the versions validated against it, and `0.5.0` is one of them; anything unlisted is still refused. Fix: **NetBox and Branching patch releases no longer block the plugin** — the plugin refused to load on anything but NetBox `4.6.5` and `netbox-branching` `1.1.1`, so a patch upgrade to `4.6.6` or `1.1.2` stopped it dead; and where it did load, the version-pinned engines switched themselves off silently. Any `4.6.x` and `1.1.x` is accepted now, with behavioural contract checks in place of version equality: the set-based merge engine verifies the live MAC search index and the fast baseline verifies the fields of every model it loads directly, so a patch that really does change something underneath still fails closed. NetBox `4.6.6` added a third indexed MAC field, which is exactly what that check catches — set-based merge (opt-in, off by default) declines there until it emits that field. Release flow: the gate no longer has to run on port 8000, and an upgrade from the previous release is tested before publish rather than only a clean install. No migration and no operator step; upgrade and re-run the sync. |
|
|
@@ -64,15 +64,16 @@ See the
|
|
|
64
64
|
|
|
65
65
|
## Release Compatibility
|
|
66
66
|
|
|
67
|
-
The `2.7.
|
|
67
|
+
The `2.7.4` release candidate requires NetBox `4.6.x` (>= `4.6.5`, tested on `4.6.6`) and `netbox-branching` `1.1.x` (tested on `1.1.2`). Expand for the published release history and candidate notes.
|
|
68
68
|
|
|
69
69
|
<details>
|
|
70
70
|
<summary>Release compatibility history</summary>
|
|
71
71
|
|
|
72
72
|
| Plugin Release | NetBox Version | Status |
|
|
73
73
|
| --- | --- | --- |
|
|
74
|
-
| `v2.7.
|
|
75
|
-
| `v2.7.
|
|
74
|
+
| `v2.7.4` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Release candidate. Published as `2.7.4`: `2.7.3` was tagged but never published, so no `2.7.3` artifact exists on PyPI - the release commit recorded no `## Release Authorization` evidence, and the publish workflow refuses a tag whose plan cannot show which gates ran against that exact tree, so it stopped before building anything. 2.7.4 is the same tree with that evidence recorded. Fix: **one interface NetBox refuses no longer fails the whole sync**. A `ValidationError` raised while validating an interface left the apply engine entirely and surfaced as a terminating failure that named no model and no row, so a run that had staged 172 changes ended having applied none and the only evidence produced identified neither the device nor the interface. The row is now recorded against `dcim.interface` and the rest of the shard proceeds. Fix: **a rejection now says which rule rejected it, not just which field**. The rule catalogue read only the errors NetBox declines to attach to a field, which made a rule legible exactly when NetBox would not name a field and illegible whenever it did. `untagged_vlan` alone carries two unrelated NetBox rules - an interface whose mode admits no untagged VLAN, and a VLAN outside the device's site - with different causes and different fixes, and no evidence the plugin produced could tell them apart. Every field is read now, both rules are catalogued, and the field and the rule are reported together. Fix: **a rejection about state the sync did not write is skipped rather than failed**. Validation covers the whole object while an update writes only the fields that differ, so an untagged VLAN left behind by a device that moved sites fails a later sync that changes only the MTU. Refusing to write is right; calling it a failure is not, because a failure also fails the row's dependents and no retry can change the answer. A catalogued rule on a field the row does not write is now recorded and skipped. A rule the catalogue cannot name stays a failure, deliberately. Fix: **both apply paths reach the same verdict**. The bulk engine and the row-oriented adapter had come to opposite conclusions about the same interface while each was locally correct, and the parity harness could not see it because both write nothing in that case. One shared rule now answers it for both. Fix: **the merge stops filing its own defects as unfixable**. Every validation rejection at merge counted as unsatisfiable, which is right for a rule that belongs to the destination and wrong for a rejection the merge itself caused, where retrying is the correct response. Only a catalogued rule landing on a field the change writes moves; an uncatalogued rule still skips, so nothing can newly block a baseline. Fix: **a spent baseline is no longer called the current one**. Every ingestion that promotes leaves a baseline record behind, and promotion only marks the previous one superseded - it clears the contents but keeps the row, which goes on protecting its ingestion. The refusal told an operator with three undeletable ingestions that each was the baseline for the sync, true of at most one, and pointed at a repair the product does not offer. It now says the record is spent, that it still protects the ingestion, and that nothing releases it today. Collecting spent baselines is still a known gap. Feature: **a command that finds the interfaces NetBox will refuse**. `forward_interface_vlan_audit` reports every interface whose untagged VLAN belongs to another site, and every interface carrying one with no 802.1Q mode, naming the device, the interface, both sites and the VLAN ID. Persisted diagnostics carry schema identifiers and never collected values, which is why a rejected row cannot name itself; this computes the answer on demand and writes it to the operator's console instead. Fix: **an ingestion whose baseline has been superseded can be deleted**. Every ingestion that promotes leaves a baseline record behind, and promotion only marks the previous one superseded - it clears the contents but keeps the row, which goes on protecting its ingestion. Nothing ever removed one, so every ingestion that had promoted was permanently undeletable and the backlog grew by one per successful sync. A spent record now goes with its ingestion; the live one is still kept, by every route rather than only through the delete button. Fix: **an ingestion whose job is still running is refused**, the same protection a sync already had. Deleting one mid-flight removed the job record while its worker carried on against it. Fix: **a skipped row says which model it was waiting for**, and says skipped rather than failed. Six identical rows naming only an exception class told an operator nothing about which parent was missing. Rows skipped while pruning are the inverse case and now name the records still referencing the object, which is why the prune was refused. Compatibility: **netbox-dlm `0.7.0` is accepted**, validated against the full suite rather than accepted on its changelog. No migration and no operator step; upgrade and re-run the sync. |
|
|
75
|
+
| `v2.7.2` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Current release; Fix: **a query bound by ID no longer has to prove a commit before it can run**. A map bound to a Forward query by direct ID with no stored commit was rejected before execution, and because one ineligible map empties the whole plan, nothing was scheduled and the sync ended having fetched nothing - while every Forward call returned 200. The plugin was resolving a commit on Forward's behalf by walking the query history for one whose source matched its own bundled copy; Forward never required it, and the execution payload already omitted it for head. A query ID now executes at Forward's latest. An explicitly stored commit is still honoured, and diff execution is unchanged because there the two commits are part of the request path. A query whose parameters have moved on now fails on its own model, naming the map, instead of silently emptying the run. Fix: **a run whose model comes back far smaller than its baseline is refused**. Dropping the commit check removed the comparison that used to notice a query changing underneath a map, which left one case invisible: a query that still accepts the same parameters and still returns the same shape, but fewer rows. Those rows are authoritative, so the absent ones reconcile as deletions with nothing reported. A model returning more than 30% fewer rows than its last promoted baseline, and at least 20 fewer, now blocks the run before anything is staged. It is on by default and a sync with no drift policy is still covered; a policy can widen or disable it, the absence of one cannot. A first run, a diff run, a model already failing, and a scope the operator narrowed are all excluded. Fix: **a failed sync now says why, per model**. The reason was reduced to an exception class name before anything recorded it, so no log, database row, or support bundle could recover it - one deployment ran five consecutive failed syncs that could not be diagnosed from any evidence it produced. Failures now carry a catalogued reason alongside the class, resolved once in a shared place rather than as another per-case exception. Two paths that composed their own message, and two job outcomes that were exported as redacted because they were phrased differently, were folded into it. Messages that embed collected values are still reduced to wording and slugs. Release flow: **a post-release commit that carries executable content fails the harness**. The first commit after a release tag must be documentation only, and it cannot be reclaimed once taken, so getting it wrong makes every later release unverifiable. No migration beyond the drift-policy fields, which apply themselves, and no operator step; upgrade and re-run the sync. |
|
|
76
|
+
| `v2.7.1` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.7.2`; Fix: **a merge delete blocked by a hidden PROTECT reference is now predicted instead of discovered at apply time**. 2.7.0 shipped this as a known limitation because widening the predictor on reasoning alone kept a device that should have been deleted; the predictor now accounts for the provenance rows the merge clears inside the same transaction, so a delete is scheduled when nothing outside the merge still protects the row and is still refused when something does. Fix: **a CVE and the software version referencing it no longer fail when both are created in the same merge**. The dependency graph modelled scalar foreign keys but not many-to-many references, so the target could be ordered after the row pointing at it. Latent since 2.5.11 and only reachable once the CVE maps were enabled. Fix: **deleting an ingestion now takes its own scope-reconciliation rows with it**. Those rows protect the ingestion, so an ingestion that had reconciled could never be removed and the refusal named a model the operator has no way to clear. Fix: **the ingestion issues list no longer returns a 500**. The view was registered but its URL was never included, so the list route resolved to nothing while the detail route worked. A test now sweeps every model with a detail route for the same omission. Fix: **a row NetBox can never accept is now recorded as skipped rather than failed**. Any failed row blocks baseline promotion, and without promotion there is no bookkeeping and drift reports Not measured, so a handful of permanently unsatisfiable addresses could mask thousands of correct changes. Integrity and unrecognised errors still fail and still block. Security: takes the `cryptography` fix for CVE-2026-69247. Release flow: dependency advisories are audited before the gate rather than after the tag, a skipped post-release step now fails the harness instead of passing quietly, and a release opens its own close-out work. No migration and no operator step; upgrade and re-run the sync. |
|
|
76
77
|
| `v2.7.0` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.7.1`; Fix: **every ingestion was undeletable, and the refusal explained nothing**. `ForwardIngestionProvenanceMixin` declares its ingestion foreign key as PROTECT with `related_name="+"`, and Django hides such relations from `_meta.related_objects` - which is exactly what the protection check iterated, so of the five PROTECT relations pointing at an ingestion it could see one. An ingestion held only by device identities reported no refusal, so the delete view fell through to NetBox's confirmation page, rendered one dependent row per synced device, and then failed with `ProtectedError` on confirm. Every ingestion owns one identity per device, which is why the count grew with every sync rather than staying at one. Fix: **a primary IP moving between devices now converges**. The ownership proof was read through the active branch, and a branch is a snapshot of the moment it was provisioned, so an identity finalized after that provision was invisible and read as "not ours" - suppressing a release we were entitled to make. The holder lookup and the writes stay branch-native; only the ownership proof is pinned to the control plane. Feature: **CIMC firmware is reported into device lifecycle**. netbox-dlm `0.6.0` adds `InventoryItemSoftware`, so the firmware running on each Cisco management controller now lands in DLM alongside every device's OS version. The version is read from the SNMP sysDescr Forward already collects, so the map requests no additional collection; an endpoint whose sysDescr carries no firmware clause yields no row rather than a guessed one. Opt-in, and it attaches to the inventory items the CIMC endpoint map creates. Fix: **an optional plugin version that did not match exactly silently disabled its whole integration**. `required_package_version` was an equality gate, and an unmatched version skips every model of that integration; it now expresses a set of supported versions, the same shape the fast baseline already uses for the same reason. Known limitation: a merge delete blocked by one of those hidden PROTECT relations is still not predicted, so it is scheduled and fails at apply time, and a failed row blocks baseline promotion. Widening the merge predictor was tried and measured: it silently kept a device that should have been deleted, because the merge clears that reference in the same transaction. Deferred rather than shipped on reasoning. No migration and no operator step; upgrade and re-run the sync. |
|
|
77
78
|
| `v2.6.12` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.7.0`; Published as `2.6.12`: `2.6.10` and `2.6.11` were tagged but never published, so no artifact exists for either and PyPI went from `2.6.9` to this release. 2.6.12 carries both tranches plus the release-path fix below. Fix: **a merge failure that names no field now names its rule**. A non-field validation error reports `__all__` — a field name meaning "no field" — so a customer's `ipam.ipaddress` merge failure was indistinguishable from any other cause. NetBox interpolates the message before raising, putting the address inside the string, so it cannot be persisted as-is: known rules resolve to slugs this plugin defines, and anything uncatalogued is reduced to wording with every value-bearing token masked. Confirmed in the field: a failure that had read only `ValidationError` now reads `violating primary-ip-reassignment-blocked`. Fix: **a row NetBox refuses on its own validation rule no longer wedges the baseline**. Any failed row blocks baseline promotion, and re-running cannot change a validation rejection because the rule belongs to the destination rather than the incoming data, so a few permanently unsatisfiable addresses could stop thousands of correct changes from ever being attested. Those rows are recorded and skipped; integrity and other errors still fail and still block. Fix: **the ingestion log download contains the issues behind the run** — message, exception and structured diagnosis, with an explicit truncation flag past 200 rows. The exception text was previously discarded before anything persisted it, so searching server logs for it could not have matched on any deployment. Fix: **the one ingestion that cannot be deleted no longer reads as an error** — it is the baseline, the durable record of what has already converged, and NetBox protects it deliberately; the refusal now says so and is raised as a warning. Fix: **the scope reconciliation refresh button no longer returns a 500 while also queueing a job that never runs**. The view handed the job class to the queue where a callable was required; RQ rejects a class, but only at dispatch, which happens on transaction commit — after the Job row is already written. Fix: **a failed ownership reconciliation names which conflict refused it**. Eight sites raised the error bare and the message was stripped before the job record was written, so the customer-visible failure carried the exception class and nothing else. The messages embed device names, so an allowlist maps each known conflict to a slug, and an unmatched message is recorded as unrecognised rather than falling silent. Release flow: **the release provenance gate is now satisfiable**. Two of its requirements had not been jointly satisfiable since the 2.6 series began: every commit in the reviewed range must still have a successful main-push run, which GitHub expires, while the security-bootstrap check required the three scanner files to appear in a diff from the anchor — true only while the anchor sat immediately before the single commit that introduced them. A fixed anchor expires runs and a moved anchor empties that diff, which is why `2.6.10` and then `2.6.11` both failed at the tag. The scanner is now verified at the release commit against pinned digests, which is strictly stronger than the diff test it replaces because that test never looked at content, and which frees the anchor to advance. No migration and no operator step; upgrade and re-run the sync. |
|
|
78
79
|
| `v2.6.9` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.6`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.6.12`; Published as `2.6.9`: `2.6.7` and `2.6.8` were tagged but never published, so no artifact exists for either — a release-time gate resolved the version to upgrade from by reading git tags, and a tag records only what was tagged, not what reached the index. 2.6.9 carries both tranches plus the fixes below. Fix: **a merge failure names the constraint or field it violated** in the issue list, not only in the record behind it — the detail had been stored since 2.6.6 but the list still showed just the exception class, so a merge `ValidationError` looked identical whatever caused it. Fix: **the one ingestion that cannot be deleted says so first**. Deleting the baseline ingestion rendered every dependent object — one per synced device — and only then refused, so the refusal arrived after several hundred device names. Release flow: the upgrade gate moved out of the tag-triggered publish into pull-request CI, where a failure blocks a merge rather than consuming a version number that cannot be reused, and the version to upgrade from is resolved from the package index rather than from tags. Fix: **a module whose type re-creates a component another module already owns** no longer fails the row. NetBox adopts an existing component only when it belongs to no module, so a console port already claimed by a different module can be neither adopted nor recreated and the database rejects the duplicate name; because a failed row blocks baseline promotion, one such module stalled a whole sync. The collision is now detected before the write and reported as a rolled-up skip. Fix: **two devices resolving the same management address** no longer fail the ingestion. NetBox allows an address to be the primary IP of exactly one device, and the `Mgmt_`-tag path assigned it without checking, so a shared management address ended the sync at `dcim_device_primary_ip4_id_key` after every workload had already been staged. Each address is claimed once and the conflicts are reported. Release flow: the build job now checks out with tags, and a harness check fails closed if a job running a tag-dependent task ever loses them again. Convergence, diagnosability and apply-performance release. Fix: **a device could fail to adopt its primary IP** with `The specified IP address is not assigned to this device` — when the device and the address both already existed and a sync only re-pointed them, nothing ordered the two updates and whichever change was recorded first won. A race, which is why it presented as a single device out of thousands and never reproduced. Fix: **a delete the database will always refuse no longer fails the row** — a delete held by a PROTECT reference that survives the run was still attempted, and because any failed row blocks baseline promotion, one operator-owned object could wedge a sync's convergence bookkeeping indefinitely. It is now reported and skipped, leaving the intent visible in drift. Fix: **a failed sync explains itself in the UI** — a terminating failure recorded no model and no detail, so the one issue that says why a run died was the least informative thing on the page and the constraint was recoverable only from server logs. Constraint, table and invalid-field names are now recorded on every sync failure, in the issue message and over the API; the key values behind them still are not. Fix: **one invalid MAC row no longer aborts the whole batch**, and two stored rows sharing a canonical address are reported instead of one being overwritten at random. Feature: **an opt-in COPY/SQL apply engine for MAC addresses**, off by default and version-pinned, measured 8.98x faster on a realistic mix with full fallback to the existing engine. Performance: **IP address creation issues a third fewer queries** by not re-fetching an interface and device that validation never needed. Feature: **a license-tier refusal from Forward is named as one** — organization-authored queries require a NETWORK-facet tier and CVE data additionally requires SECURITY, so the failure says which capability is missing instead of surfacing a raw HTTP error. Fix: **an optional plugin can be upgraded again** — `netbox-dlm` was pinned to an exact `0.4.1`, so `0.5.0` was refused at install; worse, the fast baseline, set-based merge and COPY/SQL engines each compared the whole optional-plugin tuple exactly, so getting past the install pin would have switched all three off with nothing an operator could see — turning a first sync from minutes into hours. Each engine now lists the versions validated against it, and `0.5.0` is one of them; anything unlisted is still refused. Fix: **NetBox and Branching patch releases no longer block the plugin** — the plugin refused to load on anything but NetBox `4.6.5` and `netbox-branching` `1.1.1`, so a patch upgrade to `4.6.6` or `1.1.2` stopped it dead; and where it did load, the version-pinned engines switched themselves off silently. Any `4.6.x` and `1.1.x` is accepted now, with behavioural contract checks in place of version equality: the set-based merge engine verifies the live MAC search index and the fast baseline verifies the fields of every model it loads directly, so a patch that really does change something underneath still fails closed. NetBox `4.6.6` added a third indexed MAC field, which is exactly what that check catches — set-based merge (opt-in, off by default) declines there until it emits that field. Release flow: the gate no longer has to run on port 8000, and an upgrade from the previous release is tested before publish rather than only a clean install. No migration and no operator step; upgrade and re-run the sync. |
|
|
@@ -5,7 +5,7 @@ class NetboxForwardConfig(PluginConfig):
|
|
|
5
5
|
name = "forward_netbox"
|
|
6
6
|
verbose_name = "Forward"
|
|
7
7
|
description = "Sync Forward data into NetBox using built-in NQE queries."
|
|
8
|
-
version = "2.7.
|
|
8
|
+
version = "2.7.4"
|
|
9
9
|
base_url = "forward"
|
|
10
10
|
min_version = "4.6.5"
|
|
11
11
|
max_version = "4.6.99"
|
|
@@ -70,6 +70,7 @@ class ForwardDataError(ForwardSyncError):
|
|
|
70
70
|
defaults: dict | None = None,
|
|
71
71
|
data: dict | None = None,
|
|
72
72
|
issue_id: int | None = None,
|
|
73
|
+
dependency: str | None = None,
|
|
73
74
|
):
|
|
74
75
|
super().__init__(message)
|
|
75
76
|
self.model_string = model_string
|
|
@@ -77,6 +78,13 @@ class ForwardDataError(ForwardSyncError):
|
|
|
77
78
|
self.defaults = defaults or {}
|
|
78
79
|
self.data = data or {}
|
|
79
80
|
self.issue_id = issue_id
|
|
81
|
+
# The model whose absence (or whose surviving children) caused this.
|
|
82
|
+
# A schema identifier the plugin defines, so unlike `context` — which
|
|
83
|
+
# carries the device or platform NAME and is reduced to key names before
|
|
84
|
+
# anything persists it — this one can be recorded. Without it a skipped
|
|
85
|
+
# row records only the exception class, and a customer reading six
|
|
86
|
+
# identical rows has no way to learn which parent was missing.
|
|
87
|
+
self.dependency = dependency or ""
|
|
80
88
|
|
|
81
89
|
|
|
82
90
|
class ForwardSearchError(ForwardDataError):
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from django.core.management.base import BaseCommand
|
|
4
|
+
|
|
5
|
+
from forward_netbox.utilities.interface_vlan_audit import audit_interface_untagged_vlans
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Command(BaseCommand):
|
|
9
|
+
help = (
|
|
10
|
+
"Read-only audit of interfaces NetBox will refuse on their untagged VLAN. "
|
|
11
|
+
"Reports each interface whose untagged VLAN belongs to a different site "
|
|
12
|
+
"than its device (and is not global), and each interface carrying an "
|
|
13
|
+
"untagged VLAN with no 802.1Q mode. A sync records which rule rejected a "
|
|
14
|
+
"row but not which row, because persisted diagnostics carry no customer "
|
|
15
|
+
"data — this names the interfaces on your console instead. Never writes."
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def add_arguments(self, parser):
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--limit",
|
|
21
|
+
type=int,
|
|
22
|
+
default=25,
|
|
23
|
+
help="Rows listed per rule. Counts are always exact; 0 lists none.",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def handle(self, *args, **options):
|
|
27
|
+
payload = audit_interface_untagged_vlans(
|
|
28
|
+
sample_limit=int(options.get("limit") or 0)
|
|
29
|
+
)
|
|
30
|
+
self.stdout.write(json.dumps(payload, indent=2, default=str))
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import django.db.models.deletion
|
|
2
|
+
from django.db import migrations
|
|
3
|
+
from django.db import models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Contributor baselines are main-schema convergence evidence, like the ownership
|
|
7
|
+
# rows in 0047. Branch schemas must not receive independent constraints for them.
|
|
8
|
+
fake_on_branch = True
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Migration(migrations.Migration):
|
|
12
|
+
"""Let an ingestion take its spent contributor baseline with it.
|
|
13
|
+
|
|
14
|
+
`ForwardContributorBaseline.ingestion` was PROTECT, and nothing anywhere
|
|
15
|
+
deletes a baseline. Promotion marks the previous generation SUPERSEDED,
|
|
16
|
+
deletes its relations and empties its payload — but keeps the row, which
|
|
17
|
+
goes on protecting its ingestion. So every ingestion that ever promoted
|
|
18
|
+
became permanently undeletable and the backlog grew by one per successful
|
|
19
|
+
sync; a customer reported three at once, and the refusal named a record he
|
|
20
|
+
had no way to remove.
|
|
21
|
+
|
|
22
|
+
The live baseline must still be kept, and CASCADE alone would not keep it.
|
|
23
|
+
That guarantee moves from the database to `refuse_ingestion_delete_with_live
|
|
24
|
+
_baseline` in `signals.py`, a `pre_delete` receiver, which fires for
|
|
25
|
+
querysets as well as instances and so covers every path the PROTECT
|
|
26
|
+
constraint covered. It is a deliberate trade: PROTECT could not express
|
|
27
|
+
"keep this one, collect that one", and expressing it in Python is what makes
|
|
28
|
+
the spent rows collectable at all.
|
|
29
|
+
|
|
30
|
+
Reversible: the reverse restores PROTECT. It can fail only if an ingestion
|
|
31
|
+
was deleted while this was applied and its spent baseline went with it —
|
|
32
|
+
which is the point of applying it — and no reverse resurrects those rows.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
dependencies = [
|
|
36
|
+
("forward_netbox", "0049_drift_policy_row_shrink_floor"),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
operations = [
|
|
40
|
+
migrations.AlterField(
|
|
41
|
+
model_name="forwardcontributorbaseline",
|
|
42
|
+
name="ingestion",
|
|
43
|
+
field=models.OneToOneField(
|
|
44
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
45
|
+
related_name="contributor_baseline",
|
|
46
|
+
to="forward_netbox.forwardingestion",
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
]
|
|
@@ -1168,7 +1168,13 @@ class ForwardContributorBaseline(ForwardPluginModelDocsMixin, models.Model):
|
|
|
1168
1168
|
)
|
|
1169
1169
|
ingestion = models.OneToOneField(
|
|
1170
1170
|
ForwardIngestion,
|
|
1171
|
-
|
|
1171
|
+
# CASCADE, not PROTECT: promotion leaves the previous generation
|
|
1172
|
+
# SUPERSEDED with its relations deleted and its payload emptied, and
|
|
1173
|
+
# nothing ever removed that husk, so every ingestion that promoted
|
|
1174
|
+
# became permanently undeletable. PROTECT cannot express "keep the live
|
|
1175
|
+
# one, collect the spent one"; the live one is kept by a `pre_delete`
|
|
1176
|
+
# receiver instead - see `refuse_ingestion_delete_with_live_baseline`.
|
|
1177
|
+
on_delete=models.CASCADE,
|
|
1172
1178
|
related_name="contributor_baseline",
|
|
1173
1179
|
)
|
|
1174
1180
|
parent_baseline = models.ForeignKey(
|
|
@@ -143,6 +143,59 @@ def cancel_enqueued_jobs_on_sync_delete(sender, instance, **kwargs):
|
|
|
143
143
|
job.delete()
|
|
144
144
|
|
|
145
145
|
|
|
146
|
+
@receiver(pre_delete, sender=ForwardIngestion)
|
|
147
|
+
def refuse_ingestion_delete_with_live_baseline(sender, instance, **kwargs):
|
|
148
|
+
"""Keep the live contributor baseline, and any ingestion still running.
|
|
149
|
+
|
|
150
|
+
Two guarantees that used to belong to the database, moved here when
|
|
151
|
+
`ForwardContributorBaseline.ingestion` became CASCADE so spent baselines
|
|
152
|
+
could be collected. PROTECT could not distinguish the live generation from
|
|
153
|
+
a superseded husk; this can, and unlike a check in the delete view it fires
|
|
154
|
+
for `queryset.delete()` too, so no path loses a baseline the sync still
|
|
155
|
+
depends on.
|
|
156
|
+
|
|
157
|
+
The running-job half closes the same hole `cancel_enqueued_jobs_on_sync_delete`
|
|
158
|
+
closes for a sync. `ForwardIngestion` carries `JobsMixin` as well, so its
|
|
159
|
+
`Job` rows cascade through the SQL collector and bypass `Job.delete()`'s
|
|
160
|
+
RQ-cancel override - deleting a mid-flight ingestion would drop the job row
|
|
161
|
+
while its worker kept running against it.
|
|
162
|
+
"""
|
|
163
|
+
from .models import ForwardContributorBaseline
|
|
164
|
+
|
|
165
|
+
baseline = ForwardContributorBaseline.objects.filter(
|
|
166
|
+
ingestion=instance,
|
|
167
|
+
is_current=True,
|
|
168
|
+
).first()
|
|
169
|
+
if baseline is not None:
|
|
170
|
+
raise ProtectedError(
|
|
171
|
+
"Cannot delete the ingestion holding this sync's current "
|
|
172
|
+
"contributor baseline; run the sync again so a newer ingestion "
|
|
173
|
+
"promotes its own, then delete this one.",
|
|
174
|
+
[baseline],
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
ingestion_type = ContentType.objects.get_for_model(
|
|
178
|
+
ForwardIngestion, for_concrete_model=False
|
|
179
|
+
)
|
|
180
|
+
referenced_job_ids = {
|
|
181
|
+
job_id
|
|
182
|
+
for job_id in (instance.job_id, instance.merge_job_id)
|
|
183
|
+
if job_id is not None
|
|
184
|
+
}
|
|
185
|
+
running = list(
|
|
186
|
+
Job.objects.filter(
|
|
187
|
+
Q(object_type=ingestion_type, object_id=instance.pk)
|
|
188
|
+
| Q(pk__in=referenced_job_ids),
|
|
189
|
+
status=JobStatusChoices.STATUS_RUNNING,
|
|
190
|
+
).order_by("pk")
|
|
191
|
+
)
|
|
192
|
+
if running:
|
|
193
|
+
raise ProtectedError(
|
|
194
|
+
"Cannot delete a Forward ingestion while one of its jobs is " "running.",
|
|
195
|
+
running,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
146
199
|
def acquire_job_schedule_transaction_lock():
|
|
147
200
|
"""Serialize deletion through the surrounding transaction's commit."""
|
|
148
201
|
if not connection.in_atomic_block:
|
|
@@ -1036,3 +1036,51 @@ class DependencySkipIssueRollupTest(TestCase):
|
|
|
1036
1036
|
self.assertEqual(
|
|
1037
1037
|
ForwardIngestionIssue.objects.filter(ingestion=ingestion).count(), 4
|
|
1038
1038
|
)
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
class DependencySkipNamesTheMissingParentTest(SimpleTestCase):
|
|
1042
|
+
"""A skipped row must say which parent was missing.
|
|
1043
|
+
|
|
1044
|
+
A customer's ingestion showed six rows reading exactly
|
|
1045
|
+
`netbox_dlm.softwareversion row processing failed (ForwardDependencySkipError).`
|
|
1046
|
+
The raiser's own message names the platform, but it embeds the platform NAME
|
|
1047
|
+
so it cannot be persisted, and `context` is reduced to key names before
|
|
1048
|
+
storage. The model label is a schema identifier, so it can be.
|
|
1049
|
+
"""
|
|
1050
|
+
|
|
1051
|
+
def test_the_missing_platform_is_named_on_the_exception(self):
|
|
1052
|
+
from forward_netbox.exceptions import ForwardDependencySkipError
|
|
1053
|
+
from forward_netbox.utilities.sync_dlm import _lookup_platform
|
|
1054
|
+
|
|
1055
|
+
runner = Mock()
|
|
1056
|
+
runner._get_unique_or_raise.return_value = None
|
|
1057
|
+
with self.assertRaises(ForwardDependencySkipError) as caught:
|
|
1058
|
+
_lookup_platform(
|
|
1059
|
+
runner,
|
|
1060
|
+
{"platform": "some-platform", "platform_slug": "some-platform"},
|
|
1061
|
+
"netbox_dlm.softwareversion",
|
|
1062
|
+
"DLM software version",
|
|
1063
|
+
)
|
|
1064
|
+
self.assertEqual("dcim.platform", caught.exception.dependency)
|
|
1065
|
+
|
|
1066
|
+
def test_the_missing_device_type_is_named(self):
|
|
1067
|
+
from forward_netbox.exceptions import ForwardDependencySkipError
|
|
1068
|
+
from forward_netbox.utilities.sync_dlm import _lookup_device_type
|
|
1069
|
+
|
|
1070
|
+
runner = Mock()
|
|
1071
|
+
runner._get_unique_or_raise.return_value = None
|
|
1072
|
+
with self.assertRaises(ForwardDependencySkipError) as caught:
|
|
1073
|
+
_lookup_device_type(
|
|
1074
|
+
runner,
|
|
1075
|
+
{"device_type": "some-model", "device_type_slug": "some-model"},
|
|
1076
|
+
"netbox_dlm.hardwarenotice",
|
|
1077
|
+
"DLM hardware notice",
|
|
1078
|
+
)
|
|
1079
|
+
self.assertEqual("dcim.devicetype", caught.exception.dependency)
|
|
1080
|
+
|
|
1081
|
+
def test_a_raiser_that_names_nothing_still_records_what_it_did_before(self):
|
|
1082
|
+
# Most of the plugin's 30-odd raisers are untouched, and they must keep
|
|
1083
|
+
# today's behaviour rather than record an empty slug.
|
|
1084
|
+
from forward_netbox.exceptions import ForwardDependencySkipError
|
|
1085
|
+
|
|
1086
|
+
self.assertEqual("", ForwardDependencySkipError("nope").dependency)
|