forward-netbox 2.2.4__tar.gz → 2.3.0__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.4/README.md → forward_netbox-2.3.0/PKG-INFO +59 -7
- forward_netbox-2.2.4/PKG-INFO → forward_netbox-2.3.0/README.md +25 -31
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/__init__.py +2 -2
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/forms.py +57 -2
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/jobs.py +51 -6
- forward_netbox-2.3.0/forward_netbox/management/commands/forward_metrics.py +105 -0
- forward_netbox-2.3.0/forward_netbox/management/commands/forward_stuck_job_alert.py +76 -0
- forward_netbox-2.3.0/forward_netbox/migrations/0031_encrypt_source_password.py +41 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/models.py +18 -4
- forward_netbox-2.3.0/forward_netbox/queries/forward_device_feature_tags.nqe +26 -0
- forward_netbox-2.3.0/forward_netbox/tests/test_crypto.py +67 -0
- forward_netbox-2.3.0/forward_netbox/tests/test_dependency_preview_summary.py +45 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_device_scope_reconciliation_audit_command.py +21 -1
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_device_scope_tagging.py +9 -9
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_forms.py +31 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_forward_api.py +44 -5
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_interface_naming.py +3 -2
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_issue_rendering.py +0 -1
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_jobs.py +91 -0
- forward_netbox-2.3.0/forward_netbox/tests/test_migrations.py +55 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_models.py +88 -4
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_query_registry.py +28 -7
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_scope_module_ui.py +1 -1
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sensitive_content.py +23 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync.py +62 -39
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync_facade.py +4 -3
- forward_netbox-2.3.0/forward_netbox/utilities/crypto.py +65 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/density_learning.py +0 -109
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/forward_api_impl.py +47 -2
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/interface_naming.py +1 -1
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/model_validation.py +17 -2
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_fetch_execution.py +17 -1
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_registry.py +7 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/scope_reconciliation.py +23 -3
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sensitive_content.py +35 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/single_branch_executor.py +1 -1
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_facade.py +0 -6
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_state.py +0 -2
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/views.py +17 -8
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/pyproject.toml +18 -3
- forward_netbox-2.2.4/forward_netbox/queries/forward_device_feature_tags.nqe +0 -20
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/LICENSE +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/api/__init__.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/api/serializers.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/api/urls.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/api/views.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/choices.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/exceptions.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/filtersets.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/__init__.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/__init__.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_apic_cimc_readiness_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_apply_identity_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_blocker_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_collection_gap_alert.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_device_scope_reconciliation_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_link_vsys_parents.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_module_readiness.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_primary_ip_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_pushdown_profile.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_query_diff_coverage_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_scope_ipam_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_seed_ui_harness.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_tag_delete_eligible.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_validation_org_query_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0001_initial.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0002_add_coalesce_fields.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0003_forwardingestionissue_context_fields.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0004_forwardingestion_baseline_fields.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0005_validation_and_reporting.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0006_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0007_forwardingestion_persisted_change_counters.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0008_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0009_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0010_forwardvalidationrun_override_applied_and_more.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0011_forwardingestion_change_request_id.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0012_forwardnqemap_query_reference.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0013_execution_run_step.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0014_execution_step_fetch_scope.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0015_execution_step_apply_engine.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0016_execution_step_query_metrics.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0017_execution_step_row_counters.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0018_execution_run_reconciliation_events.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0019_forwardexecutionstep_query_parameters.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0020_execution_step_operation.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0021_add_fhrpgroup_nqe_map_choice.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0022_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0023_split_ip_address_maps.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0024_forwarddeviceanalysis.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0025_device_analysis_netboxmodel.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0026_forwarddeviceanalysis_collection_result.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0027_alter_forwardexecutionrun_backend.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0028_remove_forwardexecutionstep_run_and_more.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0029_forward_parent_device_cf.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0030_forwarddeviceanalysis_cve_ids.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/__init__.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/navigation.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/__init__.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_apic_cimc_inventory.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_apic_nodes.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_app_profiles.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_bridge_domains.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_command_inventory.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_contracts.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_endpoint_groups.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_fabrics.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_filters.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_l3outs.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_nodes.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_pods.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_static_port_bindings.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_tenants.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_vrfs.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_bgp_address_families.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_bgp_peer_address_families.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_bgp_peers.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_analysis.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_feature_tags_with_rules.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_models.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_models_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_types.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_vendors.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_devices.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_devices_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_hsrp_groups.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_inferred_interface_cables.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_interfaces.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_inventory_items.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ip_addresses_ipv4.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ip_addresses_ipv6.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ip_addresses_unassignable_diagnostics.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_locations.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_mac_addresses.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_modules.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ospf_areas.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ospf_instances.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ospf_interfaces.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_peering_sessions.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_platforms.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_prefixes_ipv4.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_prefixes_ipv6.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_routing_import_diagnostics.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_virtual_chassis.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_vlans.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_vrfs.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/netbox_utilities.nqe +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/signals.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/static/forward_netbox/img/fn-logo.svg +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tables.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/template_content.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwarddriftpolicy.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardingestion.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardnqemap.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsource.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync_dependency_preview.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync_drift_report.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync_health.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync_module_readiness.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync_scope_reconciliation.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardvalidationrun.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/brand_bar.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/device_analysis_panel.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/diff.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/execution_insights.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/logs_pending.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/merge_form.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/validation_force_allow_form.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/change_explainability.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/ingestion_all.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/ingestion_merge_button.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/ingestion_progress.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/ingestion_statistics.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/ingestion_status.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/job_logs.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/object_tabs.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/stage_switcher_buttons.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/validation_force_allow_button.html +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templatetags/__init__.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templatetags/forward_netbox_helpers.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/__init__.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/fixtures/aci_command_inventory_expected.json +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/fixtures/aci_discovery_expected.json +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/scenarios.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_api_usage.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_api_views.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_apic_cimc_readiness_audit_command.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_apply_engine.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_apply_identity_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_branching.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_brand_bar.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_bulk_adapter_parity.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_bulk_merge.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_bulk_merge_scale.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_device_analysis_query.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_execution_insights_delete_label.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_health.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_ingestion_merge.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_ip_address_dedup_query.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_ipaddress_dup_global.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_job_compat.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_job_liveness.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_log_export.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_logging.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_module_readiness.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_module_readiness_rollup.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_plugin_integrations.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_primary_ip.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_primary_ip_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_primary_ip_integration.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_query_binding.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_query_diff_coverage_audit_command.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_release_readiness_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_runtime_dependency_check.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_scope_ipam_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_scope_matched_tags.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_stability_hardening.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync_aci.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync_orchestration.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync_runner_contracts.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync_state.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_synthetic_scenarios.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_validation_org_query_audit_command.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_vsys_parent.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/urls.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/__init__.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/api_usage.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/apply_engine.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/apply_engine_bulk.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/apply_engine_decision.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/apply_identity_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/branch_budget.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/branching.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/bulk_merge.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/change_explainability.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/device_analysis.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/direct_changes.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/drift_report.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/execution_ledger.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/execution_telemetry.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/fast_bootstrap_executor.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/fetch_artifacts.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/forward_api.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/health.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/health_apply_fetch.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/health_checks.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/health_summary_blocks.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/ingestion_issues.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/ingestion_merge.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/ingestion_presentation.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/job_compat.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/job_liveness.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/json_safe.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/logging.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/merge.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/model_contracts.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/module_readiness.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/multi_branch_lifecycle.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/plugin_integrations/__init__.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/plugin_integrations/registry.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/primary_ip.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/primary_ip_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_binding.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_binding_resolution.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_diagnostics.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_fetch.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/runtime_guidance.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/scope_ipam_audit.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/snapshot_freshness.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/support_bundle_archive.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_aci.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_cable.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_contracts.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_core_models.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_device.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_events.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_execution.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_interface.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_inventory_module.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_ipam.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_orchestration.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_primitives.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_reporting.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_routing.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_routing_impl.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_runner_adapters.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_runner_contracts.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/validation.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/validation_org_query.py +0 -0
- {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/vsys_parent.py +0 -0
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: forward-netbox
|
|
3
|
+
Version: 2.3.0
|
|
4
|
+
Summary: NetBox plugin to sync Forward data into NetBox via built-in NQE queries
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: netbox,forward,plugin,sync,nqe
|
|
8
|
+
Author: Craig Johnson
|
|
9
|
+
Author-email: craigjohnson@forwardnetworks.com
|
|
10
|
+
Requires-Python: >=3.10,<4.0
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Framework :: Django
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: System :: Networking
|
|
24
|
+
Requires-Dist: httpx (>0.26,<0.29)
|
|
25
|
+
Requires-Dist: netboxlabs-netbox-branching (>=1.1.0)
|
|
26
|
+
Requires-Dist: pyzipper (>=0.3.6,<0.4.0)
|
|
27
|
+
Project-URL: Changelog, https://github.com/forwardnetworks/forward-netbox/blob/main/CHANGELOG.md
|
|
28
|
+
Project-URL: Documentation, https://github.com/forwardnetworks/forward-netbox/tree/main/docs
|
|
29
|
+
Project-URL: Homepage, https://github.com/forwardnetworks/forward-netbox
|
|
30
|
+
Project-URL: Issue Tracker, https://github.com/forwardnetworks/forward-netbox/issues
|
|
31
|
+
Project-URL: Repository, https://github.com/forwardnetworks/forward-netbox
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
1
34
|
# Forward Field Integration
|
|
2
35
|
|
|
3
36
|
**Forward Field Integration** (`forward_netbox`) is a NetBox plugin that syncs Forward inventory into NetBox through direct Forward API connectivity and NQE while preserving the branch-backed sync, diff, and merge workflow.
|
|
@@ -8,7 +41,9 @@ Forward 26.6 is the baseline for async NQE.
|
|
|
8
41
|
|
|
9
42
|
| Plugin Release | NetBox Version | Status |
|
|
10
43
|
| --- | --- | --- |
|
|
11
|
-
| `v2.
|
|
44
|
+
| `v2.3.0` | `4.6.4` required; needs netbox-branching `1.1.0+` | Current release; GA/enterprise hardening: encrypted Forward credential at rest, PyPI Trusted Publishing + SBOM, Prometheus metrics + stuck-job alert, populated-DB upgrade test, dead-code removal (multi_branch/density-learning), reliability fixes (jittered/Retry-After backoff, SaaS rate clamp, PK-anchored device prune), and supported-product framing. Drop-in from 2.2.5 — stored credentials auto-encrypt on save; rotating SECRET_KEY requires re-entering them. |
|
|
45
|
+
| `v2.2.5` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.3.0`; Feature: operator-selectable **Sync Device Tags** — pick which Forward device tags (e.g. `Mgmt_*`) become NetBox device tags (replaces the hardcoded feature-tag set); Fix dependency-preview AttributeError + vsys job pile-up guard (hung pending); test/require NetBox 4.6.4 |
|
|
46
|
+
| `v2.2.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.5`; Hotfix: device-analysis NQE (bare foreach) errored refresh + CVE list; surface job errors into job.data |
|
|
12
47
|
| `v2.2.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.4`; Field-feedback fixes: delete-count labeling, vsys/vdom auto-link, skip empty VRFs, per-device CVE list, churn pinpoint, query-ID status clarify |
|
|
13
48
|
| `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 |
|
|
14
49
|
| `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 |
|
|
@@ -16,7 +51,6 @@ Forward 26.6 is the baseline for async NQE.
|
|
|
16
51
|
| `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) |
|
|
17
52
|
| `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 |
|
|
18
53
|
| `v2.1.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.4`; Prune empty orphan sites (zero devices + zero racks) alongside out-of-scope devices |
|
|
19
|
-
| `v2.1.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.3`; Prune empty orphan sites (zero devices + zero racks) alongside out-of-scope devices |
|
|
20
54
|
| `v2.1.2` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.3`; Feature + docs: (1) new out-of-scope orphan health signal — the sync health summary now shows how many NetBox devices match none of the included Forward tags (removable via Scope Reconciliation -> Prune orphans), mirroring the backfilled signal, via a self-healing `forward-out-of-scope` device tag and a `?tag=forward-out-of-scope` filter; (2) docs: the "no covering prefix" diagnostic now names /32 and /128 host addresses (loopbacks, anycast, some VIPs), and the Operations Guide documents backfilled (in-scope, kept) vs out-of-scope (removable) devices. Drop-in from `2.1.1`. |
|
|
21
55
|
| `v2.1.1` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.2`; Bugfix + diagnostics: (1) the IPv4/IPv6 IP queries global dedup now pins the chosen interface to the chosen device (mirroring the VRF and MAC dedup blocks), so a deduped global address can no longer be attributed to an interface on a different device — the source of spurious "target interface was not imported" skips; (2) new read-only `forward_primary_ip_audit` command buckets Mgmt_ primary-IP resolution per device (resolvable / device-not-in-netbox / interface-not-matched / interface-present-no-IP) to pinpoint why a device does not get a primary IP. Drop-in from `2.1.0`. |
|
|
22
56
|
| `v2.1.0` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.1`; Feature: `forward_scope_ipam_audit` management command — a read-only audit listing network-global IPAM (prefixes, VLANs, VRFs) that NetBox holds but the sync's latest Forward fetch no longer reports, as manual-review candidates. Device-tag scope prune is device-derived and never removes global IPAM; this surfaces stale global objects without deleting anything (identity matching reuses the apply engine so verdicts match the sync). Drop-in from `2.0.8`. |
|
|
@@ -32,7 +66,6 @@ Forward 26.6 is the baseline for async NQE.
|
|
|
32
66
|
| `v1.7.2` | `4.6.3` required (4.5.x dropped); needs netbox-branching `1.1.0+` | Superseded by `v2.0.0`; Collection-gap diagnostics: per-reason backfill breakdown + staleness, growth/trend escalation, per-device collection result, ACI delete safety valve, opt-in auto-tag |
|
|
33
67
|
| `v1.7.1` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.2`; ACI BD/L3Out graduation + FHRP churn fix (replaces yanked 1.7.0 and 1.6.2) |
|
|
34
68
|
| `v1.7.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.1`; ACI bridge domain and L3Out NQE maps; query publish hardening |
|
|
35
|
-
| `v1.7.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.0`; ACI bridge domain and L3Out NQE maps; query publish hardening |
|
|
36
69
|
| `v1.6.2` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.0`; completes the 1.6.1 line (1.6.1 was yanked — its PyPI build predated these): device tag scope now covers VLANs/VRFs and prefixes derive from connected interface subnets; the FHRP group churn (delete+recreate every sync) is fixed by identity-bucket sharding; device analysis is a first-class model with a fleet list view, REST API, and an Open in Forward deep-link. |
|
|
37
70
|
| `v1.6.1` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.6.2`; matures the 1.6.0 features and tooling — Device Analysis is now a NetBox model with a fleet-wide list view, REST API, and per-device-FK panel scoping (with up-interface blast-radius and opt-in post-sync refresh); adds a schedulable collection-gap alert command, run-history drill-down links, and hardened release tooling (one-command release script, generated CHANGELOG, conventional-commit hook). |
|
|
38
71
|
| `v1.6.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.6.1`; ships the blue-sky tranche — release automation (`invoke release`), an Operations Guide, a collection-gap health signal, a sync run-history panel, a read-only device analysis panel (GA reachability / connectivity-degree blast radius / CVE exposure), and a bidirectional per-model drift report. |
|
|
@@ -184,9 +217,21 @@ Forward 26.6 is the baseline for async NQE.
|
|
|
184
217
|
| `v0.1.1` | Added pagination, shared built-in NQE helpers, and release/doc cleanup. |
|
|
185
218
|
| `v0.1.0` | Initial unsupported NetBox plugin release with built-in Forward sync workflow and seeded NQE maps. |
|
|
186
219
|
|
|
187
|
-
## Support
|
|
220
|
+
## Support
|
|
221
|
+
|
|
222
|
+
Forward Field Integration is an officially maintained Forward Networks integration
|
|
223
|
+
for NetBox. Supported NetBox and `netbox-branching` versions are listed in the
|
|
224
|
+
Release Compatibility table above; support targets the latest released version.
|
|
188
225
|
|
|
189
|
-
|
|
226
|
+
- **Bugs / feature requests:** open a GitHub issue using the provided templates.
|
|
227
|
+
- **Security vulnerabilities:** report privately per [SECURITY.md](SECURITY.md) —
|
|
228
|
+
do not open a public issue.
|
|
229
|
+
- **Upgrades:** follow the [Upgrade and Rollback guide](docs/01_User_Guide/upgrade.md);
|
|
230
|
+
back up the NetBox database before upgrading.
|
|
231
|
+
|
|
232
|
+
Deploy on a supported NetBox version and review the deployment security notes in
|
|
233
|
+
`SECURITY.md` (credential-at-rest and the sync trust boundary) before production
|
|
234
|
+
use.
|
|
190
235
|
|
|
191
236
|
## Features
|
|
192
237
|
|
|
@@ -226,10 +271,16 @@ This repository is provided for use at your own risk. It is an unsupported relea
|
|
|
226
271
|
|
|
227
272
|
1. Install the plugin into the same Python environment as NetBox:
|
|
228
273
|
|
|
229
|
-
Install the
|
|
274
|
+
Install the latest release from PyPI:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pip install forward-netbox
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Or install a specific wheel or source archive from GitHub Releases:
|
|
230
281
|
|
|
231
282
|
```bash
|
|
232
|
-
pip install /path/to/forward_netbox-
|
|
283
|
+
pip install /path/to/forward_netbox-2.2.5-py3-none-any.whl
|
|
233
284
|
```
|
|
234
285
|
|
|
235
286
|
2. Enable both plugins in the NetBox configuration:
|
|
@@ -314,3 +365,4 @@ Normal UI/API sync jobs default to native multi-branch execution, with a default
|
|
|
314
365
|
- [Model Mapping Matrix](docs/02_Reference/model-mapping-matrix.md)
|
|
315
366
|
- [Shipped NQE Query Files](https://github.com/forwardnetworks/forward-netbox/tree/main/forward_netbox/queries)
|
|
316
367
|
- [License](LICENSE)
|
|
368
|
+
|
|
@@ -1,26 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: forward-netbox
|
|
3
|
-
Version: 2.2.4
|
|
4
|
-
Summary: NetBox plugin to sync Forward data into NetBox via built-in NQE queries
|
|
5
|
-
License: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Keywords: netbox,forward,plugin,sync,nqe
|
|
8
|
-
Author: Craig Johnson
|
|
9
|
-
Author-email: craigjohnson@forwardnetworks.com
|
|
10
|
-
Requires-Python: >=3.10,<4.0
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
-
Requires-Dist: httpx (>0.26,<0.29)
|
|
20
|
-
Requires-Dist: netboxlabs-netbox-branching (>=1.1.0)
|
|
21
|
-
Requires-Dist: pyzipper (>=0.3.6,<0.4.0)
|
|
22
|
-
Description-Content-Type: text/markdown
|
|
23
|
-
|
|
24
1
|
# Forward Field Integration
|
|
25
2
|
|
|
26
3
|
**Forward Field Integration** (`forward_netbox`) is a NetBox plugin that syncs Forward inventory into NetBox through direct Forward API connectivity and NQE while preserving the branch-backed sync, diff, and merge workflow.
|
|
@@ -31,7 +8,9 @@ Forward 26.6 is the baseline for async NQE.
|
|
|
31
8
|
|
|
32
9
|
| Plugin Release | NetBox Version | Status |
|
|
33
10
|
| --- | --- | --- |
|
|
34
|
-
| `v2.
|
|
11
|
+
| `v2.3.0` | `4.6.4` required; needs netbox-branching `1.1.0+` | Current release; GA/enterprise hardening: encrypted Forward credential at rest, PyPI Trusted Publishing + SBOM, Prometheus metrics + stuck-job alert, populated-DB upgrade test, dead-code removal (multi_branch/density-learning), reliability fixes (jittered/Retry-After backoff, SaaS rate clamp, PK-anchored device prune), and supported-product framing. Drop-in from 2.2.5 — stored credentials auto-encrypt on save; rotating SECRET_KEY requires re-entering them. |
|
|
12
|
+
| `v2.2.5` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.3.0`; Feature: operator-selectable **Sync Device Tags** — pick which Forward device tags (e.g. `Mgmt_*`) become NetBox device tags (replaces the hardcoded feature-tag set); Fix dependency-preview AttributeError + vsys job pile-up guard (hung pending); test/require NetBox 4.6.4 |
|
|
13
|
+
| `v2.2.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.5`; Hotfix: device-analysis NQE (bare foreach) errored refresh + CVE list; surface job errors into job.data |
|
|
35
14
|
| `v2.2.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.4`; Field-feedback fixes: delete-count labeling, vsys/vdom auto-link, skip empty VRFs, per-device CVE list, churn pinpoint, query-ID status clarify |
|
|
36
15
|
| `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 |
|
|
37
16
|
| `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 |
|
|
@@ -39,7 +18,6 @@ Forward 26.6 is the baseline for async NQE.
|
|
|
39
18
|
| `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) |
|
|
40
19
|
| `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 |
|
|
41
20
|
| `v2.1.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.4`; Prune empty orphan sites (zero devices + zero racks) alongside out-of-scope devices |
|
|
42
|
-
| `v2.1.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.3`; Prune empty orphan sites (zero devices + zero racks) alongside out-of-scope devices |
|
|
43
21
|
| `v2.1.2` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.3`; Feature + docs: (1) new out-of-scope orphan health signal — the sync health summary now shows how many NetBox devices match none of the included Forward tags (removable via Scope Reconciliation -> Prune orphans), mirroring the backfilled signal, via a self-healing `forward-out-of-scope` device tag and a `?tag=forward-out-of-scope` filter; (2) docs: the "no covering prefix" diagnostic now names /32 and /128 host addresses (loopbacks, anycast, some VIPs), and the Operations Guide documents backfilled (in-scope, kept) vs out-of-scope (removable) devices. Drop-in from `2.1.1`. |
|
|
44
22
|
| `v2.1.1` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.2`; Bugfix + diagnostics: (1) the IPv4/IPv6 IP queries global dedup now pins the chosen interface to the chosen device (mirroring the VRF and MAC dedup blocks), so a deduped global address can no longer be attributed to an interface on a different device — the source of spurious "target interface was not imported" skips; (2) new read-only `forward_primary_ip_audit` command buckets Mgmt_ primary-IP resolution per device (resolvable / device-not-in-netbox / interface-not-matched / interface-present-no-IP) to pinpoint why a device does not get a primary IP. Drop-in from `2.1.0`. |
|
|
45
23
|
| `v2.1.0` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.1`; Feature: `forward_scope_ipam_audit` management command — a read-only audit listing network-global IPAM (prefixes, VLANs, VRFs) that NetBox holds but the sync's latest Forward fetch no longer reports, as manual-review candidates. Device-tag scope prune is device-derived and never removes global IPAM; this surfaces stale global objects without deleting anything (identity matching reuses the apply engine so verdicts match the sync). Drop-in from `2.0.8`. |
|
|
@@ -55,7 +33,6 @@ Forward 26.6 is the baseline for async NQE.
|
|
|
55
33
|
| `v1.7.2` | `4.6.3` required (4.5.x dropped); needs netbox-branching `1.1.0+` | Superseded by `v2.0.0`; Collection-gap diagnostics: per-reason backfill breakdown + staleness, growth/trend escalation, per-device collection result, ACI delete safety valve, opt-in auto-tag |
|
|
56
34
|
| `v1.7.1` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.2`; ACI BD/L3Out graduation + FHRP churn fix (replaces yanked 1.7.0 and 1.6.2) |
|
|
57
35
|
| `v1.7.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.1`; ACI bridge domain and L3Out NQE maps; query publish hardening |
|
|
58
|
-
| `v1.7.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.0`; ACI bridge domain and L3Out NQE maps; query publish hardening |
|
|
59
36
|
| `v1.6.2` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.0`; completes the 1.6.1 line (1.6.1 was yanked — its PyPI build predated these): device tag scope now covers VLANs/VRFs and prefixes derive from connected interface subnets; the FHRP group churn (delete+recreate every sync) is fixed by identity-bucket sharding; device analysis is a first-class model with a fleet list view, REST API, and an Open in Forward deep-link. |
|
|
60
37
|
| `v1.6.1` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.6.2`; matures the 1.6.0 features and tooling — Device Analysis is now a NetBox model with a fleet-wide list view, REST API, and per-device-FK panel scoping (with up-interface blast-radius and opt-in post-sync refresh); adds a schedulable collection-gap alert command, run-history drill-down links, and hardened release tooling (one-command release script, generated CHANGELOG, conventional-commit hook). |
|
|
61
38
|
| `v1.6.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.6.1`; ships the blue-sky tranche — release automation (`invoke release`), an Operations Guide, a collection-gap health signal, a sync run-history panel, a read-only device analysis panel (GA reachability / connectivity-degree blast radius / CVE exposure), and a bidirectional per-model drift report. |
|
|
@@ -207,9 +184,21 @@ Forward 26.6 is the baseline for async NQE.
|
|
|
207
184
|
| `v0.1.1` | Added pagination, shared built-in NQE helpers, and release/doc cleanup. |
|
|
208
185
|
| `v0.1.0` | Initial unsupported NetBox plugin release with built-in Forward sync workflow and seeded NQE maps. |
|
|
209
186
|
|
|
210
|
-
## Support
|
|
187
|
+
## Support
|
|
188
|
+
|
|
189
|
+
Forward Field Integration is an officially maintained Forward Networks integration
|
|
190
|
+
for NetBox. Supported NetBox and `netbox-branching` versions are listed in the
|
|
191
|
+
Release Compatibility table above; support targets the latest released version.
|
|
211
192
|
|
|
212
|
-
|
|
193
|
+
- **Bugs / feature requests:** open a GitHub issue using the provided templates.
|
|
194
|
+
- **Security vulnerabilities:** report privately per [SECURITY.md](SECURITY.md) —
|
|
195
|
+
do not open a public issue.
|
|
196
|
+
- **Upgrades:** follow the [Upgrade and Rollback guide](docs/01_User_Guide/upgrade.md);
|
|
197
|
+
back up the NetBox database before upgrading.
|
|
198
|
+
|
|
199
|
+
Deploy on a supported NetBox version and review the deployment security notes in
|
|
200
|
+
`SECURITY.md` (credential-at-rest and the sync trust boundary) before production
|
|
201
|
+
use.
|
|
213
202
|
|
|
214
203
|
## Features
|
|
215
204
|
|
|
@@ -249,10 +238,16 @@ This repository is provided for use at your own risk. It is an unsupported relea
|
|
|
249
238
|
|
|
250
239
|
1. Install the plugin into the same Python environment as NetBox:
|
|
251
240
|
|
|
252
|
-
Install the
|
|
241
|
+
Install the latest release from PyPI:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
pip install forward-netbox
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Or install a specific wheel or source archive from GitHub Releases:
|
|
253
248
|
|
|
254
249
|
```bash
|
|
255
|
-
pip install /path/to/forward_netbox-
|
|
250
|
+
pip install /path/to/forward_netbox-2.2.5-py3-none-any.whl
|
|
256
251
|
```
|
|
257
252
|
|
|
258
253
|
2. Enable both plugins in the NetBox configuration:
|
|
@@ -337,4 +332,3 @@ Normal UI/API sync jobs default to native multi-branch execution, with a default
|
|
|
337
332
|
- [Model Mapping Matrix](docs/02_Reference/model-mapping-matrix.md)
|
|
338
333
|
- [Shipped NQE Query Files](https://github.com/forwardnetworks/forward-netbox/tree/main/forward_netbox/queries)
|
|
339
334
|
- [License](LICENSE)
|
|
340
|
-
|
|
@@ -8,9 +8,9 @@ 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.
|
|
11
|
+
version = "2.3.0"
|
|
12
12
|
base_url = "forward"
|
|
13
|
-
min_version = "4.6.
|
|
13
|
+
min_version = "4.6.4"
|
|
14
14
|
|
|
15
15
|
def ready(self):
|
|
16
16
|
super().ready()
|
|
@@ -436,6 +436,19 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
436
436
|
"filtered in NetBox."
|
|
437
437
|
),
|
|
438
438
|
)
|
|
439
|
+
self.fields["sync_device_tags"] = FlexibleMultipleChoiceField(
|
|
440
|
+
required=False,
|
|
441
|
+
choices=(),
|
|
442
|
+
widget=APISelectMultiple(
|
|
443
|
+
api_url="/api/plugins/forward/source/available-tags/"
|
|
444
|
+
),
|
|
445
|
+
label="Sync Device Tags",
|
|
446
|
+
help_text=(
|
|
447
|
+
"Forward device tags (e.g. Mgmt_*) to sync onto NetBox devices as "
|
|
448
|
+
"device tags. Select one or more; leave empty to sync none. "
|
|
449
|
+
"Independent of the include/exclude scope filters above."
|
|
450
|
+
),
|
|
451
|
+
)
|
|
439
452
|
_configure_api_select(
|
|
440
453
|
self.fields["network_id"].widget,
|
|
441
454
|
{
|
|
@@ -448,6 +461,7 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
448
461
|
)
|
|
449
462
|
self.fields["device_tag_include_tags"].widget.attrs["multiple"] = "multiple"
|
|
450
463
|
self.fields["device_tag_exclude_tags"].widget.attrs["multiple"] = "multiple"
|
|
464
|
+
self.fields["sync_device_tags"].widget.attrs["multiple"] = "multiple"
|
|
451
465
|
_configure_api_select(
|
|
452
466
|
self.fields["device_tag_include_tags"].widget,
|
|
453
467
|
{
|
|
@@ -470,6 +484,17 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
470
484
|
"network_id": "$network_id",
|
|
471
485
|
},
|
|
472
486
|
)
|
|
487
|
+
_configure_api_select(
|
|
488
|
+
self.fields["sync_device_tags"].widget,
|
|
489
|
+
{
|
|
490
|
+
"type": "$type",
|
|
491
|
+
"url": "$url",
|
|
492
|
+
"username": "$username",
|
|
493
|
+
"password": "$password",
|
|
494
|
+
"verify": "$verify",
|
|
495
|
+
"network_id": "$network_id",
|
|
496
|
+
},
|
|
497
|
+
)
|
|
473
498
|
if self.instance.pk:
|
|
474
499
|
self.fields["network_id"].widget.add_query_param(
|
|
475
500
|
"source_id", self.instance.pk
|
|
@@ -480,6 +505,9 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
480
505
|
self.fields["device_tag_exclude_tags"].widget.add_query_param(
|
|
481
506
|
"source_id", self.instance.pk
|
|
482
507
|
)
|
|
508
|
+
self.fields["sync_device_tags"].widget.add_query_param(
|
|
509
|
+
"source_id", self.instance.pk
|
|
510
|
+
)
|
|
483
511
|
|
|
484
512
|
parameters = self.instance.parameters or {}
|
|
485
513
|
existing_username = parameters.get("username")
|
|
@@ -549,9 +577,11 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
549
577
|
self.fields["network_id"].choices = _selected_choice(existing_network_id)
|
|
550
578
|
include_bound = []
|
|
551
579
|
exclude_bound = []
|
|
580
|
+
sync_tags_bound = []
|
|
552
581
|
if self.is_bound:
|
|
553
582
|
include_bound = self._bound_tag_values("device_tag_include_tags")
|
|
554
583
|
exclude_bound = self._bound_tag_values("device_tag_exclude_tags")
|
|
584
|
+
sync_tags_bound = self._bound_tag_values("sync_device_tags")
|
|
555
585
|
include_initial = (
|
|
556
586
|
include_bound
|
|
557
587
|
if self.is_bound
|
|
@@ -568,14 +598,22 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
568
598
|
if exclude_initial is None and parameters.get("device_tag_exclude"):
|
|
569
599
|
exclude_initial = [parameters.get("device_tag_exclude")]
|
|
570
600
|
exclude_initial = self._normalize_tag_values(exclude_initial)
|
|
601
|
+
sync_tags_initial = (
|
|
602
|
+
sync_tags_bound if self.is_bound else parameters.get("sync_device_tags")
|
|
603
|
+
)
|
|
604
|
+
sync_tags_initial = self._normalize_tag_values(sync_tags_initial)
|
|
571
605
|
self.fields["device_tag_include_tags"].initial = include_initial
|
|
572
606
|
self.fields["device_tag_exclude_tags"].initial = exclude_initial
|
|
607
|
+
self.fields["sync_device_tags"].initial = sync_tags_initial
|
|
573
608
|
self.fields["device_tag_include_tags"].choices = [
|
|
574
609
|
(tag, tag) for tag in include_initial
|
|
575
610
|
]
|
|
576
611
|
self.fields["device_tag_exclude_tags"].choices = [
|
|
577
612
|
(tag, tag) for tag in exclude_initial
|
|
578
613
|
]
|
|
614
|
+
self.fields["sync_device_tags"].choices = [
|
|
615
|
+
(tag, tag) for tag in sync_tags_initial
|
|
616
|
+
]
|
|
579
617
|
self.fields["device_tag_include_match"].initial = (
|
|
580
618
|
parameters.get("device_tag_include_match") or "any"
|
|
581
619
|
)
|
|
@@ -619,6 +657,7 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
619
657
|
"device_tag_filter_mode",
|
|
620
658
|
"device_tag_prune_out_of_scope",
|
|
621
659
|
"apply_device_scope_tags",
|
|
660
|
+
"sync_device_tags",
|
|
622
661
|
name="Parameters",
|
|
623
662
|
)
|
|
624
663
|
)
|
|
@@ -649,6 +688,7 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
649
688
|
"device_tag_filter_mode",
|
|
650
689
|
"device_tag_prune_out_of_scope",
|
|
651
690
|
"apply_device_scope_tags",
|
|
691
|
+
"sync_device_tags",
|
|
652
692
|
name="Parameters",
|
|
653
693
|
)
|
|
654
694
|
)
|
|
@@ -690,6 +730,11 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
690
730
|
exclude_tags = self._normalize_tag_values(
|
|
691
731
|
self._bound_tag_values("device_tag_exclude_tags")
|
|
692
732
|
)
|
|
733
|
+
sync_device_tags = self._normalize_tag_values(cleaned.get("sync_device_tags"))
|
|
734
|
+
if not sync_device_tags:
|
|
735
|
+
sync_device_tags = self._normalize_tag_values(
|
|
736
|
+
self._bound_tag_values("sync_device_tags")
|
|
737
|
+
)
|
|
693
738
|
candidate_parameters = {
|
|
694
739
|
"username": username,
|
|
695
740
|
"password": password,
|
|
@@ -794,6 +839,7 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
794
839
|
cleaned.get("device_tag_prune_out_of_scope")
|
|
795
840
|
),
|
|
796
841
|
"apply_device_scope_tags": bool(cleaned.get("apply_device_scope_tags")),
|
|
842
|
+
"sync_device_tags": sync_device_tags,
|
|
797
843
|
}
|
|
798
844
|
self.instance.type = source_type
|
|
799
845
|
self.instance.url = (
|
|
@@ -834,6 +880,9 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
834
880
|
self.fields["device_tag_exclude_tags"].choices = [
|
|
835
881
|
(tag, tag) for tag in exclude_tags
|
|
836
882
|
]
|
|
883
|
+
self.fields["sync_device_tags"].choices = [
|
|
884
|
+
(tag, tag) for tag in sync_device_tags
|
|
885
|
+
]
|
|
837
886
|
return cleaned
|
|
838
887
|
|
|
839
888
|
def save(self, *args, **kwargs):
|
|
@@ -861,6 +910,13 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
861
910
|
exclude_tags = self._normalize_tag_values(
|
|
862
911
|
self._bound_tag_values("device_tag_exclude_tags")
|
|
863
912
|
)
|
|
913
|
+
sync_device_tags = self._normalize_tag_values(
|
|
914
|
+
self.cleaned_data.get("sync_device_tags")
|
|
915
|
+
)
|
|
916
|
+
if not sync_device_tags:
|
|
917
|
+
sync_device_tags = self._normalize_tag_values(
|
|
918
|
+
self._bound_tag_values("sync_device_tags")
|
|
919
|
+
)
|
|
864
920
|
self.instance.parameters = {
|
|
865
921
|
"username": self.cleaned_data.get("username")
|
|
866
922
|
or existing_parameters.get("username")
|
|
@@ -976,6 +1032,7 @@ class ForwardSourceForm(NetBoxModelForm):
|
|
|
976
1032
|
"apply_device_scope_tags": bool(
|
|
977
1033
|
self.cleaned_data.get("apply_device_scope_tags")
|
|
978
1034
|
),
|
|
1035
|
+
"sync_device_tags": sync_device_tags,
|
|
979
1036
|
}
|
|
980
1037
|
self.instance.status = ForwardSourceStatusChoices.NEW
|
|
981
1038
|
return super().save(*args, **kwargs)
|
|
@@ -1205,7 +1262,6 @@ class ForwardSyncForm(NetBoxModelForm):
|
|
|
1205
1262
|
parameters = {
|
|
1206
1263
|
"execution_backend": ForwardExecutionBackendChoices.SINGLE_BRANCH,
|
|
1207
1264
|
"auto_merge": cleaned.get("auto_merge", False),
|
|
1208
|
-
"multi_branch": True,
|
|
1209
1265
|
"snapshot_id": snapshot_id,
|
|
1210
1266
|
"enable_bulk_orm": bool(cleaned.get("enable_bulk_orm", False)),
|
|
1211
1267
|
"bulk_orm_models": list(
|
|
@@ -1230,7 +1286,6 @@ class ForwardSyncForm(NetBoxModelForm):
|
|
|
1230
1286
|
parameters = {
|
|
1231
1287
|
"execution_backend": ForwardExecutionBackendChoices.SINGLE_BRANCH,
|
|
1232
1288
|
"auto_merge": self.cleaned_data.get("auto_merge", False),
|
|
1233
|
-
"multi_branch": True,
|
|
1234
1289
|
"snapshot_id": self.cleaned_data.get("snapshot_id")
|
|
1235
1290
|
or LATEST_PROCESSED_SNAPSHOT,
|
|
1236
1291
|
"enable_bulk_orm": bool(self.cleaned_data.get("enable_bulk_orm", False)),
|
|
@@ -53,8 +53,23 @@ def _resolve_request_user(*, sync, job=None):
|
|
|
53
53
|
return job.user
|
|
54
54
|
if getattr(sync, "user", None) is not None:
|
|
55
55
|
return sync.user
|
|
56
|
+
# No invoking or owning user. Inventory-wide writes and their ObjectChange
|
|
57
|
+
# attribution would otherwise SILENTLY run as an arbitrary superuser, leaving
|
|
58
|
+
# an unexplainable audit trail. Keep the run working (a user FK is required
|
|
59
|
+
# downstream) but surface the fallback loudly so an operator assigns a proper
|
|
60
|
+
# sync owner.
|
|
56
61
|
User = get_user_model()
|
|
57
|
-
|
|
62
|
+
fallback = (
|
|
63
|
+
User.objects.filter(is_active=True, is_superuser=True).order_by("pk").first()
|
|
64
|
+
)
|
|
65
|
+
logger.warning(
|
|
66
|
+
"Forward sync %s has no invoking or owning user; attributing changes to "
|
|
67
|
+
"fallback superuser '%s'. Assign an owner to the sync so inventory writes "
|
|
68
|
+
"are attributed correctly.",
|
|
69
|
+
getattr(sync, "pk", "?"),
|
|
70
|
+
getattr(fallback, "username", None) or "<none>",
|
|
71
|
+
)
|
|
72
|
+
return fallback
|
|
58
73
|
|
|
59
74
|
|
|
60
75
|
def _normalize_job_log_level(level):
|
|
@@ -121,6 +136,24 @@ def record_timeout_issue(ingestion, phase, message):
|
|
|
121
136
|
)
|
|
122
137
|
|
|
123
138
|
|
|
139
|
+
def _sync_has_active_job(sync, name):
|
|
140
|
+
"""True if a pending/running job with ``name`` already exists for ``sync``.
|
|
141
|
+
|
|
142
|
+
Post-sync overlays are enqueued after EVERY sync; a slow/large overlay (e.g.
|
|
143
|
+
the vsys parent-link's full-network fetch) can still be running when the next
|
|
144
|
+
sync fires, so without this guard duplicate jobs pile up in PENDING behind it
|
|
145
|
+
and look 'hung'. Skipping the enqueue when one is already active keeps at most
|
|
146
|
+
one overlay of each kind queued per sync.
|
|
147
|
+
"""
|
|
148
|
+
return sync.jobs.filter(
|
|
149
|
+
name=name,
|
|
150
|
+
status__in=[
|
|
151
|
+
JobStatusChoices.STATUS_PENDING,
|
|
152
|
+
JobStatusChoices.STATUS_RUNNING,
|
|
153
|
+
],
|
|
154
|
+
).exists()
|
|
155
|
+
|
|
156
|
+
|
|
124
157
|
def _maybe_enqueue_device_analysis_refresh(sync):
|
|
125
158
|
"""Opt-in: after a successful sync, refresh the device-analysis overlay.
|
|
126
159
|
|
|
@@ -132,11 +165,14 @@ def _maybe_enqueue_device_analysis_refresh(sync):
|
|
|
132
165
|
try:
|
|
133
166
|
from django.utils.module_loading import import_string
|
|
134
167
|
|
|
168
|
+
name = f"{sync.name} - refresh device analysis (auto)"
|
|
169
|
+
if _sync_has_active_job(sync, name):
|
|
170
|
+
return
|
|
135
171
|
Job.enqueue(
|
|
136
172
|
import_string("forward_netbox.jobs.refresh_forward_device_analysis"),
|
|
137
173
|
instance=sync,
|
|
138
174
|
user=sync.user,
|
|
139
|
-
name=
|
|
175
|
+
name=name,
|
|
140
176
|
)
|
|
141
177
|
except Exception as exc: # pragma: no cover - defensive
|
|
142
178
|
logger.warning("Auto device-analysis refresh enqueue failed: %s", exc)
|
|
@@ -156,11 +192,14 @@ def _maybe_enqueue_backfilled_tag_refresh(sync):
|
|
|
156
192
|
try:
|
|
157
193
|
from django.utils.module_loading import import_string
|
|
158
194
|
|
|
195
|
+
name = f"{sync.name} - tag backfilled devices (auto)"
|
|
196
|
+
if _sync_has_active_job(sync, name):
|
|
197
|
+
return
|
|
159
198
|
Job.enqueue(
|
|
160
199
|
import_string("forward_netbox.jobs.tag_forward_backfilled_devices"),
|
|
161
200
|
instance=sync,
|
|
162
201
|
user=sync.user,
|
|
163
|
-
name=
|
|
202
|
+
name=name,
|
|
164
203
|
)
|
|
165
204
|
except Exception as exc: # pragma: no cover - defensive
|
|
166
205
|
logger.warning("Auto backfilled-tag refresh enqueue failed: %s", exc)
|
|
@@ -180,11 +219,14 @@ def _maybe_enqueue_vsys_parent_link(sync):
|
|
|
180
219
|
try:
|
|
181
220
|
from django.utils.module_loading import import_string
|
|
182
221
|
|
|
222
|
+
name = f"{sync.name} - link vsys/vdom parents (auto)"
|
|
223
|
+
if _sync_has_active_job(sync, name):
|
|
224
|
+
return
|
|
183
225
|
Job.enqueue(
|
|
184
226
|
import_string("forward_netbox.jobs.link_forward_vsys_parents"),
|
|
185
227
|
instance=sync,
|
|
186
228
|
user=sync.user,
|
|
187
|
-
name=
|
|
229
|
+
name=name,
|
|
188
230
|
)
|
|
189
231
|
except Exception as exc: # pragma: no cover - defensive
|
|
190
232
|
logger.warning("Auto vsys parent-link enqueue failed: %s", exc)
|
|
@@ -202,11 +244,14 @@ def _maybe_enqueue_auto_prune(sync):
|
|
|
202
244
|
try:
|
|
203
245
|
from django.utils.module_loading import import_string
|
|
204
246
|
|
|
247
|
+
name = f"{sync.name} - prune orphans (auto)"
|
|
248
|
+
if _sync_has_active_job(sync, name):
|
|
249
|
+
return
|
|
205
250
|
Job.enqueue(
|
|
206
251
|
import_string("forward_netbox.jobs.prune_forward_orphans"),
|
|
207
252
|
instance=sync,
|
|
208
253
|
user=sync.user,
|
|
209
|
-
name=
|
|
254
|
+
name=name,
|
|
210
255
|
)
|
|
211
256
|
except Exception as exc: # pragma: no cover - defensive
|
|
212
257
|
logger.warning("Auto prune-orphans enqueue failed: %s", exc)
|
|
@@ -523,7 +568,7 @@ def create_forward_module_bays(job, *args, **kwargs):
|
|
|
523
568
|
def forward_dependency_preview(job, *args, **kwargs):
|
|
524
569
|
"""Background dependency dry-run preview for a sync.
|
|
525
570
|
|
|
526
|
-
The dry-run builds a full
|
|
571
|
+
The dry-run builds a full single-branch plan against live Forward data, which
|
|
527
572
|
far exceeds an HTTP gateway timeout on large fabrics. Run it as a job and
|
|
528
573
|
cache the JSON payload on ``job.data`` so the preview page can render it
|
|
529
574
|
later without a Forward round-trip.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from core.choices import JobStatusChoices
|
|
4
|
+
from core.models import Job
|
|
5
|
+
from django.contrib.contenttypes.models import ContentType
|
|
6
|
+
from django.core.management.base import BaseCommand
|
|
7
|
+
from django.utils import timezone
|
|
8
|
+
|
|
9
|
+
from forward_netbox.models import ForwardIngestion
|
|
10
|
+
from forward_netbox.models import ForwardSource
|
|
11
|
+
from forward_netbox.models import ForwardSync
|
|
12
|
+
from forward_netbox.utilities.job_liveness import job_has_live_execution
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("forward_netbox.metrics")
|
|
15
|
+
|
|
16
|
+
ACTIVE_JOB_STATUSES = (
|
|
17
|
+
JobStatusChoices.STATUS_PENDING,
|
|
18
|
+
JobStatusChoices.STATUS_RUNNING,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _metric(lines, name, help_text, value, mtype="gauge", labels=""):
|
|
23
|
+
lines.append(f"# HELP {name} {help_text}")
|
|
24
|
+
lines.append(f"# TYPE {name} {mtype}")
|
|
25
|
+
label_block = f"{{{labels}}}" if labels else ""
|
|
26
|
+
lines.append(f"{name}{label_block} {value}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Command(BaseCommand):
|
|
30
|
+
help = (
|
|
31
|
+
"Emit forward_netbox metrics in Prometheus text-exposition format on "
|
|
32
|
+
"stdout, for a node_exporter textfile collector or a scrape sidecar. "
|
|
33
|
+
"Read-only; safe to run on a schedule."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def handle(self, *args, **options):
|
|
37
|
+
lines = []
|
|
38
|
+
|
|
39
|
+
_metric(
|
|
40
|
+
lines,
|
|
41
|
+
"forward_sources_total",
|
|
42
|
+
"Configured Forward sources.",
|
|
43
|
+
ForwardSource.objects.count(),
|
|
44
|
+
)
|
|
45
|
+
_metric(
|
|
46
|
+
lines,
|
|
47
|
+
"forward_syncs_total",
|
|
48
|
+
"Configured Forward syncs.",
|
|
49
|
+
ForwardSync.objects.count(),
|
|
50
|
+
)
|
|
51
|
+
_metric(
|
|
52
|
+
lines,
|
|
53
|
+
"forward_ingestions_total",
|
|
54
|
+
"Recorded Forward ingestion runs.",
|
|
55
|
+
ForwardIngestion.objects.count(),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
forward_type_ids = list(
|
|
59
|
+
ContentType.objects.filter(app_label="forward_netbox").values_list(
|
|
60
|
+
"pk", flat=True
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
jobs = Job.objects.filter(object_type_id__in=forward_type_ids)
|
|
64
|
+
|
|
65
|
+
status_counts = {}
|
|
66
|
+
# ChoiceSet entries may be (value, label) or (value, label, color); take
|
|
67
|
+
# the value.
|
|
68
|
+
for choice in JobStatusChoices.CHOICES:
|
|
69
|
+
status = choice[0]
|
|
70
|
+
status_counts[status] = jobs.filter(status=status).count()
|
|
71
|
+
lines.append("# HELP forward_jobs Forward background jobs by status.")
|
|
72
|
+
lines.append("# TYPE forward_jobs gauge")
|
|
73
|
+
for status, count in status_counts.items():
|
|
74
|
+
lines.append(f'forward_jobs{{status="{status}"}} {count}')
|
|
75
|
+
|
|
76
|
+
active = jobs.filter(status__in=ACTIVE_JOB_STATUSES)
|
|
77
|
+
stuck = sum(1 for job in active if not job_has_live_execution(job))
|
|
78
|
+
_metric(
|
|
79
|
+
lines,
|
|
80
|
+
"forward_stuck_jobs",
|
|
81
|
+
"Active jobs with no live worker execution (wedged).",
|
|
82
|
+
stuck,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
last_completed = (
|
|
86
|
+
jobs.filter(status=JobStatusChoices.STATUS_COMPLETED)
|
|
87
|
+
.exclude(completed__isnull=True)
|
|
88
|
+
.order_by("-completed")
|
|
89
|
+
.first()
|
|
90
|
+
)
|
|
91
|
+
if last_completed and last_completed.completed:
|
|
92
|
+
_metric(
|
|
93
|
+
lines,
|
|
94
|
+
"forward_last_completed_job_timestamp_seconds",
|
|
95
|
+
"Unix time of the most recent completed Forward job.",
|
|
96
|
+
int(last_completed.completed.timestamp()),
|
|
97
|
+
)
|
|
98
|
+
_metric(
|
|
99
|
+
lines,
|
|
100
|
+
"forward_last_completed_job_age_seconds",
|
|
101
|
+
"Seconds since the most recent completed Forward job.",
|
|
102
|
+
int((timezone.now() - last_completed.completed).total_seconds()),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
self.stdout.write("\n".join(lines) + "\n")
|