forward-netbox 2.2.1__tar.gz → 2.2.3__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.2.1 → forward_netbox-2.2.3}/PKG-INFO +4 -2
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/README.md +3 -1
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/__init__.py +1 -1
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/jobs.py +30 -5
- forward_netbox-2.2.3/forward_netbox/migrations/0030_forwarddeviceanalysis_cve_ids.py +18 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/models.py +3 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_device_analysis.nqe +6 -5
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_vrfs.nqe +14 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwardingestion.html +1 -1
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwardsync_health.html +3 -3
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/inc/device_analysis_panel.html +8 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/inc/execution_insights.html +2 -1
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/partials/ingestion_all.html +1 -1
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_apply_identity_audit.py +32 -1
- forward_netbox-2.2.3/forward_netbox/tests/test_execution_insights_delete_label.py +42 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_jobs.py +34 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_log_export.py +60 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_models.py +4 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_scope_module_ui.py +6 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_vsys_parent.py +19 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/apply_identity_audit.py +80 -9
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/device_analysis.py +18 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/model_validation.py +6 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/query_binding_resolution.py +12 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/vsys_parent.py +32 -6
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/views.py +21 -7
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/pyproject.toml +1 -1
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/LICENSE +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/api/__init__.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/api/serializers.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/api/urls.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/api/views.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/choices.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/exceptions.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/filtersets.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/forms.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/__init__.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/__init__.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_apic_cimc_readiness_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_apply_identity_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_blocker_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_collection_gap_alert.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_device_scope_reconciliation_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_link_vsys_parents.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_module_readiness.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_primary_ip_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_pushdown_profile.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_query_diff_coverage_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_scope_ipam_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_seed_ui_harness.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_tag_delete_eligible.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/management/commands/forward_validation_org_query_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0001_initial.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0002_add_coalesce_fields.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0003_forwardingestionissue_context_fields.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0004_forwardingestion_baseline_fields.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0005_validation_and_reporting.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0006_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0007_forwardingestion_persisted_change_counters.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0008_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0009_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0010_forwardvalidationrun_override_applied_and_more.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0011_forwardingestion_change_request_id.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0012_forwardnqemap_query_reference.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0013_execution_run_step.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0014_execution_step_fetch_scope.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0015_execution_step_apply_engine.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0016_execution_step_query_metrics.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0017_execution_step_row_counters.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0018_execution_run_reconciliation_events.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0019_forwardexecutionstep_query_parameters.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0020_execution_step_operation.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0021_add_fhrpgroup_nqe_map_choice.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0022_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0023_split_ip_address_maps.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0024_forwarddeviceanalysis.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0025_device_analysis_netboxmodel.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0026_forwarddeviceanalysis_collection_result.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0027_alter_forwardexecutionrun_backend.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0028_remove_forwardexecutionstep_run_and_more.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/0029_forward_parent_device_cf.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/migrations/__init__.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/navigation.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/__init__.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_apic_cimc_inventory.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_apic_nodes.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_app_profiles.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_bridge_domains.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_command_inventory.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_contracts.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_endpoint_groups.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_fabrics.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_filters.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_l3outs.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_nodes.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_pods.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_static_port_bindings.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_tenants.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_aci_vrfs.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_bgp_address_families.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_bgp_peer_address_families.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_bgp_peers.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_device_feature_tags.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_device_feature_tags_with_rules.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_device_models.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_device_models_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_device_types.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_device_vendors.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_devices.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_devices_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_hsrp_groups.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_inferred_interface_cables.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_interfaces.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_inventory_items.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_ip_addresses_ipv4.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_ip_addresses_ipv6.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_ip_addresses_unassignable_diagnostics.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_locations.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_mac_addresses.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_modules.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_ospf_areas.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_ospf_instances.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_ospf_interfaces.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_peering_sessions.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_platforms.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_prefixes_ipv4.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_prefixes_ipv6.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_routing_import_diagnostics.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_virtual_chassis.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_vlans.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/netbox_utilities.nqe +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/signals.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/static/forward_netbox/img/fn-logo.svg +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tables.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/template_content.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwarddriftpolicy.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwardnqemap.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwardsource.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwardsync.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwardsync_dependency_preview.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwardsync_drift_report.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwardsync_module_readiness.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwardsync_scope_reconciliation.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/forwardvalidationrun.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/inc/brand_bar.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/inc/diff.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/inc/logs_pending.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/inc/merge_form.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/inc/validation_force_allow_form.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/partials/change_explainability.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/partials/ingestion_merge_button.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/partials/ingestion_progress.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/partials/ingestion_statistics.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/partials/ingestion_status.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/partials/job_logs.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/partials/object_tabs.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/partials/stage_switcher_buttons.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templates/forward_netbox/partials/validation_force_allow_button.html +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templatetags/__init__.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/templatetags/forward_netbox_helpers.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/__init__.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/fixtures/aci_command_inventory_expected.json +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/fixtures/aci_discovery_expected.json +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/scenarios.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_api_usage.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_api_views.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_apic_cimc_readiness_audit_command.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_apply_engine.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_branching.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_brand_bar.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_bulk_adapter_parity.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_bulk_merge.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_bulk_merge_scale.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_device_scope_reconciliation_audit_command.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_device_scope_tagging.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_forms.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_forward_api.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_health.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_ingestion_merge.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_interface_naming.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_ip_address_dedup_query.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_ipaddress_dup_global.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_issue_rendering.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_job_compat.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_job_liveness.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_logging.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_module_readiness.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_module_readiness_rollup.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_plugin_integrations.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_primary_ip.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_primary_ip_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_primary_ip_integration.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_query_binding.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_query_diff_coverage_audit_command.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_query_registry.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_release_readiness_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_runtime_dependency_check.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_scope_ipam_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_scope_matched_tags.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_sensitive_content.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_stability_hardening.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_sync.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_sync_aci.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_sync_facade.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_sync_orchestration.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_sync_runner_contracts.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_sync_state.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_synthetic_scenarios.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_validation_org_query_audit_command.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/urls.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/__init__.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/api_usage.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/apply_engine.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/apply_engine_bulk.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/apply_engine_decision.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/branch_budget.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/branching.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/bulk_merge.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/change_explainability.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/density_learning.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/direct_changes.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/drift_report.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/execution_ledger.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/execution_telemetry.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/fast_bootstrap_executor.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/fetch_artifacts.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/forward_api.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/forward_api_impl.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/health.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/health_apply_fetch.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/health_checks.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/health_summary_blocks.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/ingestion_issues.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/ingestion_merge.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/ingestion_presentation.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/interface_naming.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/job_compat.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/job_liveness.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/json_safe.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/logging.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/merge.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/model_contracts.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/module_readiness.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/multi_branch_lifecycle.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/plugin_integrations/__init__.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/plugin_integrations/registry.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/primary_ip.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/primary_ip_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/query_binding.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/query_diagnostics.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/query_fetch.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/query_fetch_execution.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/query_registry.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/runtime_guidance.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/scope_ipam_audit.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/scope_reconciliation.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sensitive_content.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/single_branch_executor.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/snapshot_freshness.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/support_bundle_archive.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_aci.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_cable.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_contracts.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_core_models.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_device.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_events.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_execution.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_facade.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_interface.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_inventory_module.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_ipam.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_orchestration.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_primitives.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_reporting.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_routing.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_routing_impl.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_runner_adapters.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_runner_contracts.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/sync_state.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/validation.py +0 -0
- {forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/utilities/validation_org_query.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forward-netbox
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.3
|
|
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
|
|
@@ -31,7 +31,9 @@ Forward 26.6 is the baseline for async NQE.
|
|
|
31
31
|
|
|
32
32
|
| Plugin Release | NetBox Version | Status |
|
|
33
33
|
| --- | --- | --- |
|
|
34
|
-
| `v2.2.
|
|
34
|
+
| `v2.2.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Current release; Field-feedback fixes: delete-count labeling, vsys/vdom auto-link, skip empty VRFs, per-device CVE list, churn pinpoint, query-ID status clarify |
|
|
35
|
+
| `v2.2.2` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.3`; Fix 504 gateway timeouts on large syncs: stop recomputing change-explainability on every poll during a long merge + back off poll to 15s |
|
|
36
|
+
| `v2.2.1` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.2`; Add read-only forward_apply_identity_audit diagnostic to pinpoint 1-created/1-deleted idempotency churn |
|
|
35
37
|
| `v2.2.0` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.1`; Fix devices mis-assigned the ACI platform; link Palo vsys / Fortinet vdom firewalls to their physical chassis |
|
|
36
38
|
| `v2.1.5` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.0`; Fix Prune orphans erroring on empty sites that still hold a VLAN/VM/prefix (delete only truly-empty sites) |
|
|
37
39
|
| `v2.1.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.5`; Tag delete-eligible global IPAM (prefixes/VLANs/VRFs) for manual review |
|
|
@@ -8,7 +8,9 @@ Forward 26.6 is the baseline for async NQE.
|
|
|
8
8
|
|
|
9
9
|
| Plugin Release | NetBox Version | Status |
|
|
10
10
|
| --- | --- | --- |
|
|
11
|
-
| `v2.2.
|
|
11
|
+
| `v2.2.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Current release; Field-feedback fixes: delete-count labeling, vsys/vdom auto-link, skip empty VRFs, per-device CVE list, churn pinpoint, query-ID status clarify |
|
|
12
|
+
| `v2.2.2` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.3`; Fix 504 gateway timeouts on large syncs: stop recomputing change-explainability on every poll during a long merge + back off poll to 15s |
|
|
13
|
+
| `v2.2.1` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.2`; Add read-only forward_apply_identity_audit diagnostic to pinpoint 1-created/1-deleted idempotency churn |
|
|
12
14
|
| `v2.2.0` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.1`; Fix devices mis-assigned the ACI platform; link Palo vsys / Fortinet vdom firewalls to their physical chassis |
|
|
13
15
|
| `v2.1.5` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.0`; Fix Prune orphans erroring on empty sites that still hold a VLAN/VM/prefix (delete only truly-empty sites) |
|
|
14
16
|
| `v2.1.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.5`; Tag delete-eligible global IPAM (prefixes/VLANs/VRFs) for manual review |
|
|
@@ -8,7 +8,7 @@ class NetboxForwardConfig(PluginConfig):
|
|
|
8
8
|
name = "forward_netbox"
|
|
9
9
|
verbose_name = "Forward Field Integration"
|
|
10
10
|
description = "Sync Forward data into NetBox using built-in NQE queries."
|
|
11
|
-
version = "2.2.
|
|
11
|
+
version = "2.2.3"
|
|
12
12
|
base_url = "forward"
|
|
13
13
|
min_version = "4.6.3"
|
|
14
14
|
|
|
@@ -167,13 +167,15 @@ def _maybe_enqueue_backfilled_tag_refresh(sync):
|
|
|
167
167
|
|
|
168
168
|
|
|
169
169
|
def _maybe_enqueue_vsys_parent_link(sync):
|
|
170
|
-
"""
|
|
170
|
+
"""After a successful sync, link virtual-context firewalls (Palo vsys /
|
|
171
171
|
Fortinet vdom) to their physical chassis via the ``forward_parent_device``
|
|
172
|
-
custom field.
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
custom field. Non-destructive, idempotent, and never affects the sync result.
|
|
173
|
+
|
|
174
|
+
Runs by DEFAULT (unlike the opt-in overlays): a blank ``Parent Device`` on
|
|
175
|
+
every vsys/vdom is a confusing default, so the link auto-refreshes each sync
|
|
176
|
+
unless the sync explicitly opts out with ``auto_link_vsys_parents=False``.
|
|
175
177
|
"""
|
|
176
|
-
if
|
|
178
|
+
if (sync.parameters or {}).get("auto_link_vsys_parents") is False:
|
|
177
179
|
return
|
|
178
180
|
try:
|
|
179
181
|
from django.utils.module_loading import import_string
|
|
@@ -188,6 +190,28 @@ def _maybe_enqueue_vsys_parent_link(sync):
|
|
|
188
190
|
logger.warning("Auto vsys parent-link enqueue failed: %s", exc)
|
|
189
191
|
|
|
190
192
|
|
|
193
|
+
def _maybe_enqueue_auto_prune(sync):
|
|
194
|
+
"""Opt-in: after a successful sync, run "Prune orphans" (delete out-of-scope
|
|
195
|
+
devices + empty orphan sites). OFF by default because it deletes NetBox data;
|
|
196
|
+
enable per sync with ``auto_prune_orphans=True``. The prune keeps its own
|
|
197
|
+
guards (refuses when the Forward scope returned 0 devices). Never lets a prune
|
|
198
|
+
problem affect the sync result.
|
|
199
|
+
"""
|
|
200
|
+
if not (sync.parameters or {}).get("auto_prune_orphans"):
|
|
201
|
+
return
|
|
202
|
+
try:
|
|
203
|
+
from django.utils.module_loading import import_string
|
|
204
|
+
|
|
205
|
+
Job.enqueue(
|
|
206
|
+
import_string("forward_netbox.jobs.prune_forward_orphans"),
|
|
207
|
+
instance=sync,
|
|
208
|
+
user=sync.user,
|
|
209
|
+
name=f"{sync.name} - prune orphans (auto)",
|
|
210
|
+
)
|
|
211
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
212
|
+
logger.warning("Auto prune-orphans enqueue failed: %s", exc)
|
|
213
|
+
|
|
214
|
+
|
|
191
215
|
def sync_forwardsync(job, *args, **kwargs):
|
|
192
216
|
sync = ForwardSync.objects.get(pk=job.object_id)
|
|
193
217
|
|
|
@@ -198,6 +222,7 @@ def sync_forwardsync(job, *args, **kwargs):
|
|
|
198
222
|
_maybe_enqueue_device_analysis_refresh(sync)
|
|
199
223
|
_maybe_enqueue_backfilled_tag_refresh(sync)
|
|
200
224
|
_maybe_enqueue_vsys_parent_link(sync)
|
|
225
|
+
_maybe_enqueue_auto_prune(sync)
|
|
201
226
|
job.terminate()
|
|
202
227
|
except Exception as exc:
|
|
203
228
|
safe_save_job_data(job, sync)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 6.0.6 on 2026-07-01 17:10
|
|
2
|
+
from django.db import migrations
|
|
3
|
+
from django.db import models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("forward_netbox", "0029_forward_parent_device_cf"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name="forwarddeviceanalysis",
|
|
15
|
+
name="cve_ids",
|
|
16
|
+
field=models.JSONField(blank=True, default=list),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -959,6 +959,9 @@ class ForwardDeviceAnalysis(ForwardPluginModelDocsMixin, ChangeLoggedModel):
|
|
|
959
959
|
collection_result = models.CharField(max_length=64, blank=True, default="")
|
|
960
960
|
blast_radius = models.PositiveIntegerField(default=0)
|
|
961
961
|
cve_count = models.PositiveIntegerField(default=0)
|
|
962
|
+
# The actual confirmed-vulnerable CVE IDs behind cve_count, so the device
|
|
963
|
+
# panel can list them (not just the exposure number).
|
|
964
|
+
cve_ids = models.JSONField(default=list, blank=True)
|
|
962
965
|
up_interfaces = models.PositiveIntegerField(default=0)
|
|
963
966
|
detail = models.CharField(max_length=255, blank=True, default="")
|
|
964
967
|
snapshot_id = models.CharField(max_length=100, blank=True, default="")
|
{forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/queries/forward_device_analysis.nqe
RENAMED
|
@@ -30,16 +30,17 @@ let physicalLinks =
|
|
|
30
30
|
where i.operStatus == OperStatus.UP
|
|
31
31
|
foreach l in i.links
|
|
32
32
|
select l)
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
let cveIds =
|
|
34
|
+
foreach c in device.cveFindings
|
|
35
|
+
where c.isVulnerable
|
|
36
|
+
select c.cveId
|
|
37
37
|
select {
|
|
38
38
|
name: device.name,
|
|
39
39
|
reachable: device.snapshotInfo.result == DeviceSnapshotResult.completed,
|
|
40
40
|
collection_result: toString(device.snapshotInfo.result),
|
|
41
41
|
blast_radius: physicalLinks,
|
|
42
|
-
cve_count:
|
|
42
|
+
cve_count: length(cveIds),
|
|
43
|
+
cve_ids: cveIds,
|
|
43
44
|
up_interfaces: upInterfaces,
|
|
44
45
|
detail: device.locationName
|
|
45
46
|
}
|
|
@@ -19,6 +19,20 @@ foreach ni in device.networkInstances
|
|
|
19
19
|
where ni.name != "default"
|
|
20
20
|
where toString(ni.instanceType) != "NetworkInstanceType.DEFAULT_INSTANCE"
|
|
21
21
|
where isEmpty(forward_netbox_shard_keys) || ni.name in forward_netbox_shard_keys
|
|
22
|
+
// Skip VRFs with no addressing: NetBox derives prefixes/IPs from subinterface
|
|
23
|
+
// addresses keyed by networkInstanceName, so a VRF whose subinterfaces carry no
|
|
24
|
+
// IPv4/IPv6 address imports as an empty VRF and re-imports every sync. Only emit
|
|
25
|
+
// a VRF that has at least one addressed subinterface (mirrors how the prefix/IP
|
|
26
|
+
// queries associate addressing to a VRF).
|
|
27
|
+
where !isEmpty(
|
|
28
|
+
foreach interface in device.interfaces
|
|
29
|
+
foreach subinterface in interface.subinterfaces
|
|
30
|
+
where isPresent(subinterface.networkInstanceName)
|
|
31
|
+
&& subinterface.networkInstanceName == ni.name
|
|
32
|
+
&& (!isEmpty(subinterface.ipv4.addresses)
|
|
33
|
+
|| !isEmpty(subinterface.ipv6.addresses))
|
|
34
|
+
select subinterface.networkInstanceName
|
|
35
|
+
)
|
|
22
36
|
select distinct {
|
|
23
37
|
name: ni.name,
|
|
24
38
|
rd: null : String,
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
<div id="ingestion_poll"
|
|
168
168
|
class="col col-md-12"
|
|
169
169
|
hx-get="{% url 'plugins:forward_netbox:forwardingestion_logs' pk=object.pk %}?stage={{ active_stage|default:'sync' }}"
|
|
170
|
-
hx-trigger="{% if job_running %}every
|
|
170
|
+
hx-trigger="{% if job_running %}every 15s{% else %}none{% endif %}"
|
|
171
171
|
hx-target="#ingestion_logs"
|
|
172
172
|
hx-select="#ingestion_logs"
|
|
173
173
|
hx-select-oob="#ingestion_status:innerHTML,#ingestion_progress:innerHTML,#ingestion_statistics:innerHTML,#change_explainability:innerHTML,#object_tabs:innerHTML,#ingestion_merge_button:innerHTML"
|
|
@@ -551,11 +551,11 @@
|
|
|
551
551
|
<td>{{ item.commit_message }}</td>
|
|
552
552
|
<td>
|
|
553
553
|
{% if item.severity == "pass" %}
|
|
554
|
-
<span class="badge text-bg-success">{{ item.status }}</span>
|
|
554
|
+
<span class="badge text-bg-success">{{ item.status_label|default:item.status }}</span>
|
|
555
555
|
{% elif item.severity == "warn" %}
|
|
556
|
-
<span class="badge text-bg-warning">{{ item.status }}</span>
|
|
556
|
+
<span class="badge text-bg-warning">{{ item.status_label|default:item.status }}</span>
|
|
557
557
|
{% else %}
|
|
558
|
-
<span class="badge text-bg-secondary">{{ item.status }}</span>
|
|
558
|
+
<span class="badge text-bg-secondary" title="{{ item.message }}">{{ item.status_label|default:item.status }}</span>
|
|
559
559
|
{% endif %}
|
|
560
560
|
</td>
|
|
561
561
|
<td>{{ item.message }}</td>
|
|
@@ -34,6 +34,14 @@
|
|
|
34
34
|
{% endif %}
|
|
35
35
|
</td>
|
|
36
36
|
</tr>
|
|
37
|
+
{% if analysis.cve_ids %}
|
|
38
|
+
<tr>
|
|
39
|
+
<th scope="row">{% trans "CVEs" %}</th>
|
|
40
|
+
<td class="lh-lg">
|
|
41
|
+
{% for cve in analysis.cve_ids %}<a href="https://nvd.nist.gov/vuln/detail/{{ cve|urlencode }}" target="_blank" rel="noopener"><code>{{ cve }}</code></a>{% if not forloop.last %} {% endif %}{% endfor %}
|
|
42
|
+
</td>
|
|
43
|
+
</tr>
|
|
44
|
+
{% endif %}
|
|
37
45
|
{% if analysis.detail %}
|
|
38
46
|
<tr>
|
|
39
47
|
<th scope="row">{% trans "Location" %}</th>
|
|
@@ -74,7 +74,8 @@
|
|
|
74
74
|
</div>
|
|
75
75
|
<div class="text-end text-muted small">
|
|
76
76
|
<div>{{ item.execution_mode }} / {{ item.fetch_mode }}</div>
|
|
77
|
-
|
|
77
|
+
{# delete_count is PLANNED intent from the Forward snapshot diff (rows Forward stopped reporting), computed before apply. The APPLIED delete count is the ingestion's num_deleted / change log — an item already absent from NetBox plans a delete but applies none, so these legitimately differ. Label as planned to avoid contradicting the change log. #}
|
|
78
|
+
<div>{{ item.row_count }} rows, {{ item.delete_count }} delete(s) planned</div>
|
|
78
79
|
</div>
|
|
79
80
|
</div>
|
|
80
81
|
</li>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
hx-swap-oob="true"
|
|
23
23
|
class="col col-md-12"
|
|
24
24
|
hx-get="{{ logs_url }}?stage={{ active_stage }}"
|
|
25
|
-
hx-trigger="{% if polling_done %}none{% else %}every
|
|
25
|
+
hx-trigger="{% if polling_done %}none{% else %}every 15s{% endif %}"
|
|
26
26
|
hx-target="#ingestion_logs"
|
|
27
27
|
hx-select="#ingestion_logs"
|
|
28
28
|
hx-select-oob="#ingestion_status:innerHTML,#ingestion_progress:innerHTML,#ingestion_statistics:innerHTML,#change_explainability:innerHTML,#object_tabs:innerHTML,#ingestion_merge_button:innerHTML"
|
{forward_netbox-2.2.1 → forward_netbox-2.2.3}/forward_netbox/tests/test_apply_identity_audit.py
RENAMED
|
@@ -39,6 +39,36 @@ class ApplyIdentityAuditTest(TestCase):
|
|
|
39
39
|
self.assertEqual(result["would_create_count"], 0)
|
|
40
40
|
self.assertFalse(result["churn_suspect"])
|
|
41
41
|
|
|
42
|
+
def test_churn_pairs_names_the_object_and_diff(self):
|
|
43
|
+
# The 1/1 churn signature should pinpoint the SAME logical object and the
|
|
44
|
+
# exact key disagreement (here: underscore vs dash in the slug).
|
|
45
|
+
# Both keys must differ for it to be churn (a matching name would match
|
|
46
|
+
# on the name lookup_set): NetBox slug dc_one / name "DC 1"; Forward
|
|
47
|
+
# slug dc-one / name "DC One".
|
|
48
|
+
Site.objects.create(name="DC 1", slug="dc_one")
|
|
49
|
+
rows = [{"slug": "dc-one", "name": "DC One"}]
|
|
50
|
+
result = audit_model_identity("dcim.site", rows)
|
|
51
|
+
self.assertTrue(result["churn_suspect"])
|
|
52
|
+
self.assertEqual(len(result["churn_pairs"]), 1)
|
|
53
|
+
pair = result["churn_pairs"][0]
|
|
54
|
+
self.assertIn("dc_one", pair["netbox"])
|
|
55
|
+
self.assertIn("dc-one", pair["forward"])
|
|
56
|
+
self.assertEqual(pair["differs_on"], "underscore-vs-dash")
|
|
57
|
+
self.assertGreater(pair["similarity"], 0.8)
|
|
58
|
+
|
|
59
|
+
def test_device_name_churn_is_audited(self):
|
|
60
|
+
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer
|
|
61
|
+
|
|
62
|
+
site = Site.objects.create(name="S", slug="s")
|
|
63
|
+
mfr = Manufacturer.objects.create(name="M", slug="m")
|
|
64
|
+
dt = DeviceType.objects.create(manufacturer=mfr, model="DT", slug="dt")
|
|
65
|
+
role = DeviceRole.objects.create(name="R", slug="r")
|
|
66
|
+
Device.objects.create(name="fw01-vsys1", device_type=dt, role=role, site=site)
|
|
67
|
+
# Forward now emits a trailing-space variant -> name-key flip-flop.
|
|
68
|
+
result = audit_model_identity("dcim.device", [{"name": "fw01-vsys1 "}])
|
|
69
|
+
self.assertTrue(result["churn_suspect"])
|
|
70
|
+
self.assertEqual(result["churn_pairs"][0]["differs_on"], "whitespace")
|
|
71
|
+
|
|
42
72
|
def test_audit_filters_to_enabled_models(self):
|
|
43
73
|
Site.objects.create(name="A", slug="a")
|
|
44
74
|
sync = SimpleNamespace(get_model_strings=lambda: ["dcim.site", "dcim.device"])
|
|
@@ -46,5 +76,6 @@ class ApplyIdentityAuditTest(TestCase):
|
|
|
46
76
|
payload = audit_apply_identity(
|
|
47
77
|
sync, fetch_rows=lambda s, model: canned.get(model, [])
|
|
48
78
|
)
|
|
49
|
-
|
|
79
|
+
# dcim.device is now an audited model; with no device rows it is clean.
|
|
80
|
+
self.assertEqual(payload["models_audited"], ["dcim.site", "dcim.device"])
|
|
50
81
|
self.assertEqual(payload["churn_suspect_models"], [])
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from django.template.loader import render_to_string
|
|
2
|
+
from django.test import TestCase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ExecutionInsightsDeleteLabelTest(TestCase):
|
|
6
|
+
"""The per-model panel shows delete_count, which is PLANNED intent from the
|
|
7
|
+
Forward snapshot diff (rows Forward stopped reporting), computed before apply.
|
|
8
|
+
An item already absent from NetBox plans a delete but applies none, so this
|
|
9
|
+
number legitimately differs from the applied num_deleted / change log. It must
|
|
10
|
+
be labeled "planned" so it does not read as "48 deleted" and contradict an
|
|
11
|
+
empty change log (the sync-23 confusion).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def _render(self):
|
|
15
|
+
return render_to_string(
|
|
16
|
+
"forward_netbox/inc/execution_insights.html",
|
|
17
|
+
{
|
|
18
|
+
"latest_execution_insights": {
|
|
19
|
+
"available": True,
|
|
20
|
+
"top_model_results": [
|
|
21
|
+
{
|
|
22
|
+
"model": "dcim.inventoryitem",
|
|
23
|
+
"query_name": "Forward Inventory Items",
|
|
24
|
+
"execution_mode": "query_path",
|
|
25
|
+
"fetch_mode": "nqe_parameters",
|
|
26
|
+
"row_count": 61129,
|
|
27
|
+
"delete_count": 48,
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def test_delete_count_is_labeled_planned(self):
|
|
35
|
+
html = self._render()
|
|
36
|
+
self.assertIn("48 delete(s) planned", html)
|
|
37
|
+
|
|
38
|
+
def test_bare_deleted_label_is_gone(self):
|
|
39
|
+
# The old "{n} deletes" label read as applied and contradicted the change
|
|
40
|
+
# log; it must not reappear.
|
|
41
|
+
html = self._render()
|
|
42
|
+
self.assertNotIn("48 deletes", html)
|
|
@@ -64,6 +64,40 @@ class ForwardJobsTest(TestCase):
|
|
|
64
64
|
enqueue.assert_called_once()
|
|
65
65
|
self.assertEqual(enqueue.call_args.kwargs["instance"], self.sync)
|
|
66
66
|
|
|
67
|
+
def test_vsys_parent_link_enqueues_by_default_and_respects_opt_out(self):
|
|
68
|
+
from forward_netbox.jobs import _maybe_enqueue_vsys_parent_link
|
|
69
|
+
|
|
70
|
+
# DEFAULT-ON: no parameter -> the parent-link overlay enqueues (a blank
|
|
71
|
+
# Parent Device on every vsys/vdom is a confusing default).
|
|
72
|
+
with patch("forward_netbox.jobs.Job.enqueue") as enqueue:
|
|
73
|
+
_maybe_enqueue_vsys_parent_link(self.sync)
|
|
74
|
+
enqueue.assert_called_once()
|
|
75
|
+
self.assertEqual(enqueue.call_args.kwargs["instance"], self.sync)
|
|
76
|
+
|
|
77
|
+
# Explicit opt-out with auto_link_vsys_parents=False -> no enqueue.
|
|
78
|
+
self.sync.parameters = {
|
|
79
|
+
**self.sync.parameters,
|
|
80
|
+
"auto_link_vsys_parents": False,
|
|
81
|
+
}
|
|
82
|
+
with patch("forward_netbox.jobs.Job.enqueue") as enqueue:
|
|
83
|
+
_maybe_enqueue_vsys_parent_link(self.sync)
|
|
84
|
+
enqueue.assert_not_called()
|
|
85
|
+
|
|
86
|
+
def test_auto_prune_orphans_enqueues_only_when_enabled(self):
|
|
87
|
+
from forward_netbox.jobs import _maybe_enqueue_auto_prune
|
|
88
|
+
|
|
89
|
+
# OFF by default (it deletes NetBox data): no enqueue.
|
|
90
|
+
with patch("forward_netbox.jobs.Job.enqueue") as enqueue:
|
|
91
|
+
_maybe_enqueue_auto_prune(self.sync)
|
|
92
|
+
enqueue.assert_not_called()
|
|
93
|
+
|
|
94
|
+
# Opt-in via auto_prune_orphans: enqueues the prune job.
|
|
95
|
+
self.sync.parameters = {**self.sync.parameters, "auto_prune_orphans": True}
|
|
96
|
+
with patch("forward_netbox.jobs.Job.enqueue") as enqueue:
|
|
97
|
+
_maybe_enqueue_auto_prune(self.sync)
|
|
98
|
+
enqueue.assert_called_once()
|
|
99
|
+
self.assertIn("prune orphans", enqueue.call_args.kwargs["name"])
|
|
100
|
+
|
|
67
101
|
def test_record_timeout_issue_creates_single_issue_per_ingestion_phase(self):
|
|
68
102
|
issue_1 = record_timeout_issue(
|
|
69
103
|
self.ingestion,
|
|
@@ -338,6 +338,66 @@ class ForwardIngestionLogExportViewTest(TestCase):
|
|
|
338
338
|
self.assertContains(response, "ipam.prefix 1")
|
|
339
339
|
self.assertContains(response, "vrf 1")
|
|
340
340
|
|
|
341
|
+
def test_poll_defers_change_explainability_while_running(self):
|
|
342
|
+
# Regression (504 fix): while the job is running, the 5s/15s poll must
|
|
343
|
+
# NOT recompute change_explainability — doing so on every poll piles DB
|
|
344
|
+
# load onto the web workers during a long settling merge, which is what
|
|
345
|
+
# produces the gateway 504s on large platform-reclassification syncs.
|
|
346
|
+
running_sync = ForwardSync.objects.create(
|
|
347
|
+
name="sync-running-poll",
|
|
348
|
+
source=self.source,
|
|
349
|
+
parameters={"snapshot_id": "latestProcessed"},
|
|
350
|
+
)
|
|
351
|
+
running_ing = ForwardIngestion.objects.create(
|
|
352
|
+
sync=running_sync,
|
|
353
|
+
snapshot_selector="latestProcessed",
|
|
354
|
+
snapshot_id="snap-running",
|
|
355
|
+
)
|
|
356
|
+
now = timezone.now()
|
|
357
|
+
running_job = Job.objects.create(
|
|
358
|
+
object_type=ContentType.objects.get_for_model(ForwardIngestion),
|
|
359
|
+
object_id=running_ing.pk,
|
|
360
|
+
name="running-ingestion-job",
|
|
361
|
+
user=None,
|
|
362
|
+
status=JobStatusChoices.STATUS_RUNNING,
|
|
363
|
+
job_id="333e4567-e89b-12d3-a456-426614174002",
|
|
364
|
+
created=now,
|
|
365
|
+
started=now,
|
|
366
|
+
completed=None,
|
|
367
|
+
data={},
|
|
368
|
+
)
|
|
369
|
+
running_ing.job = running_job
|
|
370
|
+
running_ing.save(update_fields=["job"])
|
|
371
|
+
|
|
372
|
+
self.client.force_login(self.user)
|
|
373
|
+
with patch("forward_netbox.views.change_explainability_summary") as mock_ce:
|
|
374
|
+
response = self.client.get(
|
|
375
|
+
reverse(
|
|
376
|
+
"plugins:forward_netbox:forwardingestion_logs",
|
|
377
|
+
kwargs={"pk": running_ing.pk},
|
|
378
|
+
),
|
|
379
|
+
headers={"HX-Request": "true"},
|
|
380
|
+
)
|
|
381
|
+
self.assertEqual(response.status_code, 200)
|
|
382
|
+
mock_ce.assert_not_called()
|
|
383
|
+
|
|
384
|
+
def test_poll_computes_change_explainability_when_done(self):
|
|
385
|
+
# The completed ingestion still computes it (the deferral is only while
|
|
386
|
+
# the job runs), so the operator sees the breakdown once it finishes.
|
|
387
|
+
self.client.force_login(self.user)
|
|
388
|
+
with patch(
|
|
389
|
+
"forward_netbox.views.change_explainability_summary",
|
|
390
|
+
return_value={"available": False},
|
|
391
|
+
) as mock_ce:
|
|
392
|
+
self.client.get(
|
|
393
|
+
reverse(
|
|
394
|
+
"plugins:forward_netbox:forwardingestion_logs",
|
|
395
|
+
kwargs={"pk": self.ingestion.pk},
|
|
396
|
+
),
|
|
397
|
+
headers={"HX-Request": "true"},
|
|
398
|
+
)
|
|
399
|
+
mock_ce.assert_called_once()
|
|
400
|
+
|
|
341
401
|
def test_export_logs_compacts_large_execution_plan_items(self):
|
|
342
402
|
sync = ForwardSync.objects.create(
|
|
343
403
|
name="sync-log-export-plan-items",
|
|
@@ -1604,6 +1604,10 @@ class ForwardSyncModelTest(TestCase):
|
|
|
1604
1604
|
parameters={
|
|
1605
1605
|
"snapshot_id": LATEST_PROCESSED_SNAPSHOT,
|
|
1606
1606
|
"dcim.device": True,
|
|
1607
|
+
# Isolate this test to the recurring-reschedule enqueue; the
|
|
1608
|
+
# vsys/vdom parent-link overlay is default-on and would add a
|
|
1609
|
+
# second enqueue (covered by its own test).
|
|
1610
|
+
"auto_link_vsys_parents": False,
|
|
1607
1611
|
},
|
|
1608
1612
|
)
|
|
1609
1613
|
user = get_user_model().objects.create_user(username="recurring-user")
|
|
@@ -384,6 +384,7 @@ class ScopeModuleUiTest(TestCase):
|
|
|
384
384
|
"collection_result": "DeviceSnapshotResult.completed",
|
|
385
385
|
"blast_radius": 7,
|
|
386
386
|
"cve_count": 2,
|
|
387
|
+
"cve_ids": ["CVE-2022-5678", "CVE-2021-1234"],
|
|
387
388
|
"up_interfaces": 5,
|
|
388
389
|
"detail": "DC1",
|
|
389
390
|
},
|
|
@@ -421,6 +422,8 @@ class ScopeModuleUiTest(TestCase):
|
|
|
421
422
|
self.assertTrue(analysis.reachable)
|
|
422
423
|
self.assertEqual(analysis.blast_radius, 7)
|
|
423
424
|
self.assertEqual(analysis.cve_count, 2)
|
|
425
|
+
# CVE IDs stored (deduped + sorted) so the panel can list them.
|
|
426
|
+
self.assertEqual(analysis.cve_ids, ["CVE-2021-1234", "CVE-2022-5678"])
|
|
424
427
|
self.assertEqual(analysis.up_interfaces, 5)
|
|
425
428
|
self.assertEqual(analysis.collection_result, "completed")
|
|
426
429
|
# The failed device surfaces the specific Forward collection error token.
|
|
@@ -442,6 +445,9 @@ class ScopeModuleUiTest(TestCase):
|
|
|
442
445
|
)
|
|
443
446
|
rendered = panel.right_page()
|
|
444
447
|
self.assertIn("Forward Analysis", rendered)
|
|
448
|
+
# The actual CVE IDs behind the exposure count are listed + linked to NVD.
|
|
449
|
+
self.assertIn("CVE-2021-1234", rendered)
|
|
450
|
+
self.assertIn("nvd.nist.gov/vuln/detail/CVE-2022-5678", rendered)
|
|
445
451
|
# Deep-link pivot into the Forward app.
|
|
446
452
|
self.assertIn("Open in Forward", rendered)
|
|
447
453
|
self.assertIn("https://fwd.app", rendered)
|
|
@@ -39,6 +39,25 @@ class VsysParentTest(TestCase):
|
|
|
39
39
|
self.assertEqual(vsys.custom_field_data.get(PARENT_DEVICE_CF), chassis.pk)
|
|
40
40
|
# The physical chassis itself is never linked.
|
|
41
41
|
self.assertIn(chassis.custom_field_data.get(PARENT_DEVICE_CF), (None, ""))
|
|
42
|
+
# The context is also modeled as a VirtualDeviceContext under the chassis.
|
|
43
|
+
from dcim.models import VirtualDeviceContext
|
|
44
|
+
|
|
45
|
+
self.assertEqual(result["vdc_created"], 1)
|
|
46
|
+
self.assertTrue(
|
|
47
|
+
VirtualDeviceContext.objects.filter(
|
|
48
|
+
device=chassis, name=vsys.name, status="active"
|
|
49
|
+
).exists()
|
|
50
|
+
)
|
|
51
|
+
# Re-running is idempotent — the VDC is found, not re-created.
|
|
52
|
+
again = link_vsys_parents(
|
|
53
|
+
None, fetch_rows=self._rows([(vsys.name, chassis.name)])
|
|
54
|
+
)
|
|
55
|
+
self.assertEqual(again["vdc_created"], 0)
|
|
56
|
+
self.assertEqual(again["vdc_existing"], 1)
|
|
57
|
+
self.assertEqual(
|
|
58
|
+
VirtualDeviceContext.objects.filter(device=chassis, name=vsys.name).count(),
|
|
59
|
+
1,
|
|
60
|
+
)
|
|
42
61
|
|
|
43
62
|
def test_orphan_parent_not_linked(self):
|
|
44
63
|
# vsys present in NetBox, but its physical chassis is not collected.
|