forward-netbox 2.2.2__tar.gz → 2.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/PKG-INFO +4 -2
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/README.md +3 -1
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/__init__.py +1 -1
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/jobs.py +65 -5
- forward_netbox-2.2.4/forward_netbox/migrations/0030_forwarddeviceanalysis_cve_ids.py +18 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/models.py +3 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_device_analysis.nqe +6 -5
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_vrfs.nqe +14 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwardsync_health.html +3 -3
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/inc/device_analysis_panel.html +8 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/inc/execution_insights.html +2 -1
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_apply_identity_audit.py +32 -1
- forward_netbox-2.2.4/forward_netbox/tests/test_device_analysis_query.py +26 -0
- forward_netbox-2.2.4/forward_netbox/tests/test_execution_insights_delete_label.py +42 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_jobs.py +34 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_models.py +4 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_scope_module_ui.py +6 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_vsys_parent.py +19 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/apply_identity_audit.py +80 -7
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/device_analysis.py +18 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/model_validation.py +6 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/query_binding_resolution.py +12 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/vsys_parent.py +32 -6
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/pyproject.toml +1 -1
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/LICENSE +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/api/__init__.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/api/serializers.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/api/urls.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/api/views.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/choices.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/exceptions.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/filtersets.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/forms.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/__init__.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/__init__.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_apic_cimc_readiness_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_apply_identity_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_blocker_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_collection_gap_alert.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_device_scope_reconciliation_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_link_vsys_parents.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_module_readiness.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_primary_ip_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_pushdown_profile.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_query_diff_coverage_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_scope_ipam_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_seed_ui_harness.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_tag_delete_eligible.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/management/commands/forward_validation_org_query_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0001_initial.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0002_add_coalesce_fields.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0003_forwardingestionissue_context_fields.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0004_forwardingestion_baseline_fields.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0005_validation_and_reporting.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0006_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0007_forwardingestion_persisted_change_counters.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0008_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0009_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0010_forwardvalidationrun_override_applied_and_more.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0011_forwardingestion_change_request_id.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0012_forwardnqemap_query_reference.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0013_execution_run_step.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0014_execution_step_fetch_scope.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0015_execution_step_apply_engine.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0016_execution_step_query_metrics.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0017_execution_step_row_counters.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0018_execution_run_reconciliation_events.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0019_forwardexecutionstep_query_parameters.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0020_execution_step_operation.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0021_add_fhrpgroup_nqe_map_choice.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0022_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0023_split_ip_address_maps.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0024_forwarddeviceanalysis.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0025_device_analysis_netboxmodel.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0026_forwarddeviceanalysis_collection_result.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0027_alter_forwardexecutionrun_backend.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0028_remove_forwardexecutionstep_run_and_more.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/0029_forward_parent_device_cf.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/migrations/__init__.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/navigation.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/__init__.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_apic_cimc_inventory.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_apic_nodes.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_app_profiles.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_bridge_domains.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_command_inventory.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_contracts.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_endpoint_groups.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_fabrics.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_filters.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_l3outs.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_nodes.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_pods.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_static_port_bindings.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_tenants.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_aci_vrfs.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_bgp_address_families.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_bgp_peer_address_families.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_bgp_peers.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_device_feature_tags.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_device_feature_tags_with_rules.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_device_models.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_device_models_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_device_types.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_device_vendors.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_devices.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_devices_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_hsrp_groups.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_inferred_interface_cables.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_interfaces.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_inventory_items.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_ip_addresses_ipv4.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_ip_addresses_ipv6.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_ip_addresses_unassignable_diagnostics.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_locations.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_mac_addresses.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_modules.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_ospf_areas.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_ospf_instances.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_ospf_interfaces.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_peering_sessions.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_platforms.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_prefixes_ipv4.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_prefixes_ipv6.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_routing_import_diagnostics.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_virtual_chassis.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/forward_vlans.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/queries/netbox_utilities.nqe +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/signals.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/static/forward_netbox/img/fn-logo.svg +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tables.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/template_content.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwarddriftpolicy.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwardingestion.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwardnqemap.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwardsource.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwardsync.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwardsync_dependency_preview.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwardsync_drift_report.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwardsync_module_readiness.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwardsync_scope_reconciliation.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/forwardvalidationrun.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/inc/brand_bar.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/inc/diff.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/inc/logs_pending.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/inc/merge_form.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/inc/validation_force_allow_form.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/partials/change_explainability.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/partials/ingestion_all.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/partials/ingestion_merge_button.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/partials/ingestion_progress.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/partials/ingestion_statistics.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/partials/ingestion_status.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/partials/job_logs.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/partials/object_tabs.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/partials/stage_switcher_buttons.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templates/forward_netbox/partials/validation_force_allow_button.html +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templatetags/__init__.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/templatetags/forward_netbox_helpers.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/__init__.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/fixtures/aci_command_inventory_expected.json +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/fixtures/aci_discovery_expected.json +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/scenarios.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_api_usage.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_api_views.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_apic_cimc_readiness_audit_command.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_apply_engine.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_branching.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_brand_bar.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_bulk_adapter_parity.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_bulk_merge.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_bulk_merge_scale.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_device_scope_reconciliation_audit_command.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_device_scope_tagging.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_forms.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_forward_api.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_health.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_ingestion_merge.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_interface_naming.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_ip_address_dedup_query.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_ipaddress_dup_global.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_issue_rendering.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_job_compat.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_job_liveness.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_log_export.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_logging.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_module_readiness.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_module_readiness_rollup.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_plugin_integrations.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_primary_ip.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_primary_ip_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_primary_ip_integration.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_query_binding.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_query_diff_coverage_audit_command.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_query_registry.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_release_readiness_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_runtime_dependency_check.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_scope_ipam_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_scope_matched_tags.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_sensitive_content.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_stability_hardening.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_sync.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_sync_aci.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_sync_facade.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_sync_orchestration.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_sync_runner_contracts.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_sync_state.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_synthetic_scenarios.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/tests/test_validation_org_query_audit_command.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/urls.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/__init__.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/api_usage.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/apply_engine.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/apply_engine_bulk.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/apply_engine_decision.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/branch_budget.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/branching.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/bulk_merge.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/change_explainability.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/density_learning.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/direct_changes.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/drift_report.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/execution_ledger.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/execution_telemetry.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/fast_bootstrap_executor.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/fetch_artifacts.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/forward_api.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/forward_api_impl.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/health.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/health_apply_fetch.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/health_checks.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/health_summary_blocks.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/ingestion_issues.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/ingestion_merge.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/ingestion_presentation.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/interface_naming.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/job_compat.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/job_liveness.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/json_safe.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/logging.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/merge.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/model_contracts.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/module_readiness.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/multi_branch_lifecycle.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/plugin_integrations/__init__.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/plugin_integrations/registry.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/primary_ip.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/primary_ip_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/query_binding.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/query_diagnostics.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/query_fetch.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/query_fetch_execution.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/query_registry.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/runtime_guidance.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/scope_ipam_audit.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/scope_reconciliation.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sensitive_content.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/single_branch_executor.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/snapshot_freshness.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/support_bundle_archive.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_aci.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_cable.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_contracts.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_core_models.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_device.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_events.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_execution.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_facade.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_interface.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_inventory_module.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_ipam.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_orchestration.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_primitives.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_reporting.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_routing.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_routing_impl.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_runner_adapters.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_runner_contracts.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/sync_state.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/validation.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/utilities/validation_org_query.py +0 -0
- {forward_netbox-2.2.2 → forward_netbox-2.2.4}/forward_netbox/views.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.4
|
|
4
4
|
Summary: NetBox plugin to sync Forward data into NetBox via built-in NQE queries
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -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.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Current release; Hotfix: device-analysis NQE (bare foreach) errored refresh + CVE list; surface job errors into job.data |
|
|
35
|
+
| `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
|
+
| `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 |
|
|
35
37
|
| `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 |
|
|
36
38
|
| `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 |
|
|
37
39
|
| `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) |
|
|
@@ -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.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Current release; Hotfix: device-analysis NQE (bare foreach) errored refresh + CVE list; surface job errors into job.data |
|
|
12
|
+
| `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
|
+
| `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 |
|
|
12
14
|
| `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 |
|
|
13
15
|
| `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 |
|
|
14
16
|
| `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) |
|
|
@@ -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.4"
|
|
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)
|
|
@@ -360,6 +385,13 @@ def refresh_forward_device_analysis(job, *args, **kwargs):
|
|
|
360
385
|
job.save(update_fields=["data"])
|
|
361
386
|
job.terminate()
|
|
362
387
|
except Exception as exc:
|
|
388
|
+
# Record the failure on the job so it is visible in the UI (the Data
|
|
389
|
+
# panel) instead of an empty Error field with null data.
|
|
390
|
+
job.data = {
|
|
391
|
+
"error": str(exc) or exc.__class__.__name__,
|
|
392
|
+
"error_type": exc.__class__.__name__,
|
|
393
|
+
}
|
|
394
|
+
job.save(update_fields=["data"])
|
|
363
395
|
job.terminate(status=JobStatusChoices.STATUS_ERRORED)
|
|
364
396
|
if type(exc) in (SyncError, JobTimeoutException):
|
|
365
397
|
logger.error(exc)
|
|
@@ -383,6 +415,13 @@ def tag_forward_backfilled_devices(job, *args, **kwargs):
|
|
|
383
415
|
job.save(update_fields=["data"])
|
|
384
416
|
job.terminate()
|
|
385
417
|
except Exception as exc:
|
|
418
|
+
# Record the failure on the job so it is visible in the UI (the Data
|
|
419
|
+
# panel) instead of an empty Error field with null data.
|
|
420
|
+
job.data = {
|
|
421
|
+
"error": str(exc) or exc.__class__.__name__,
|
|
422
|
+
"error_type": exc.__class__.__name__,
|
|
423
|
+
}
|
|
424
|
+
job.save(update_fields=["data"])
|
|
386
425
|
job.terminate(status=JobStatusChoices.STATUS_ERRORED)
|
|
387
426
|
if type(exc) in (SyncError, JobTimeoutException):
|
|
388
427
|
logger.error(exc)
|
|
@@ -439,6 +478,13 @@ def tag_forward_delete_eligible_ipam(job, *args, **kwargs):
|
|
|
439
478
|
job.save(update_fields=["data"])
|
|
440
479
|
job.terminate()
|
|
441
480
|
except Exception as exc:
|
|
481
|
+
# Record the failure on the job so it is visible in the UI (the Data
|
|
482
|
+
# panel) instead of an empty Error field with null data.
|
|
483
|
+
job.data = {
|
|
484
|
+
"error": str(exc) or exc.__class__.__name__,
|
|
485
|
+
"error_type": exc.__class__.__name__,
|
|
486
|
+
}
|
|
487
|
+
job.save(update_fields=["data"])
|
|
442
488
|
job.terminate(status=JobStatusChoices.STATUS_ERRORED)
|
|
443
489
|
if type(exc) in (SyncError, JobTimeoutException):
|
|
444
490
|
logger.error(exc)
|
|
@@ -460,6 +506,13 @@ def create_forward_module_bays(job, *args, **kwargs):
|
|
|
460
506
|
job.save(update_fields=["data"])
|
|
461
507
|
job.terminate()
|
|
462
508
|
except Exception as exc:
|
|
509
|
+
# Record the failure on the job so it is visible in the UI (the Data
|
|
510
|
+
# panel) instead of an empty Error field with null data.
|
|
511
|
+
job.data = {
|
|
512
|
+
"error": str(exc) or exc.__class__.__name__,
|
|
513
|
+
"error_type": exc.__class__.__name__,
|
|
514
|
+
}
|
|
515
|
+
job.save(update_fields=["data"])
|
|
463
516
|
job.terminate(status=JobStatusChoices.STATUS_ERRORED)
|
|
464
517
|
if type(exc) in (SyncError, JobTimeoutException):
|
|
465
518
|
logger.error(exc)
|
|
@@ -486,6 +539,13 @@ def forward_dependency_preview(job, *args, **kwargs):
|
|
|
486
539
|
job.save(update_fields=["data"])
|
|
487
540
|
job.terminate()
|
|
488
541
|
except Exception as exc:
|
|
542
|
+
# Record the failure on the job so it is visible in the UI (the Data
|
|
543
|
+
# panel) instead of an empty Error field with null data.
|
|
544
|
+
job.data = {
|
|
545
|
+
"error": str(exc) or exc.__class__.__name__,
|
|
546
|
+
"error_type": exc.__class__.__name__,
|
|
547
|
+
}
|
|
548
|
+
job.save(update_fields=["data"])
|
|
489
549
|
job.terminate(status=JobStatusChoices.STATUS_ERRORED)
|
|
490
550
|
if type(exc) in (SyncError, JobTimeoutException):
|
|
491
551
|
logger.error(exc)
|
|
@@ -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.2 → forward_netbox-2.2.4}/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,
|
|
@@ -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>
|
{forward_netbox-2.2.2 → forward_netbox-2.2.4}/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,26 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from django.test import SimpleTestCase
|
|
4
|
+
|
|
5
|
+
from forward_netbox.utilities.query_registry import read_builtin_query_source
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DeviceAnalysisQueryTest(SimpleTestCase):
|
|
9
|
+
"""Guard the device-analysis NQE against the 2.2.3 regression: a bare
|
|
10
|
+
``let x = foreach ... select ...`` is INVALID NQE — the live Forward engine
|
|
11
|
+
returns HTTP 400 (``extraneous input 'foreach'``) and the whole
|
|
12
|
+
refresh-device-analysis job errors. The local NQE linter does NOT catch this
|
|
13
|
+
(it reported 0 errors on the broken query), so this source-level test is the
|
|
14
|
+
real guard. A ``foreach`` used as a value must be wrapped in parens / a call.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def test_cve_ids_comprehension_is_parenthesized(self):
|
|
18
|
+
source = read_builtin_query_source("forward_device_analysis.nqe")
|
|
19
|
+
self.assertIn("(foreach c in device.cveFindings", source)
|
|
20
|
+
|
|
21
|
+
def test_no_bare_foreach_in_let_binding(self):
|
|
22
|
+
source = read_builtin_query_source("forward_device_analysis.nqe")
|
|
23
|
+
# A `let <name> =` immediately followed by a bare `foreach` is the invalid
|
|
24
|
+
# pattern; wrapped forms (`(foreach`, `length(foreach`) are fine.
|
|
25
|
+
bare = re.findall(r"let\s+\w+\s*=\s*\n\s*foreach\b", source)
|
|
26
|
+
self.assertEqual(bare, [], f"bare foreach in a let binding: {bare}")
|
|
@@ -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,
|
|
@@ -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.
|