forward-netbox 2.4.2__tar.gz → 2.4.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.4.2 → forward_netbox-2.4.4}/PKG-INFO +4 -2
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/README.md +3 -1
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/__init__.py +1 -1
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_devices.nqe +3 -6
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_devices_with_netbox_aliases.nqe +4 -7
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_bulk_merge.py +51 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_endpoints_import.py +131 -1
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_scope_matched_tags.py +4 -2
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/bulk_merge.py +34 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/health.py +12 -6
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/query_binding_resolution.py +1 -1
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/query_fetch_execution.py +98 -12
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/pyproject.toml +1 -1
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/LICENSE +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/api/__init__.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/api/serializers.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/api/urls.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/api/views.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/choices.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/exceptions.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/filtersets.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/forms.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/jobs.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/__init__.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/__init__.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_apic_cimc_readiness_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_apply_identity_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_blocker_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_collection_gap_alert.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_device_scope_reconciliation_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_link_vsys_parents.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_metrics.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_module_readiness.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_primary_ip_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_pushdown_profile.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_query_diff_coverage_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_scope_ipam_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_seed_ui_harness.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_stuck_job_alert.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_tag_delete_eligible.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/forward_validation_org_query_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0001_initial.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0002_add_coalesce_fields.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0003_forwardingestionissue_context_fields.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0004_forwardingestion_baseline_fields.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0005_validation_and_reporting.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0006_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0007_forwardingestion_persisted_change_counters.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0008_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0009_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0010_forwardvalidationrun_override_applied_and_more.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0011_forwardingestion_change_request_id.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0012_forwardnqemap_query_reference.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0013_execution_run_step.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0014_execution_step_fetch_scope.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0015_execution_step_apply_engine.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0016_execution_step_query_metrics.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0017_execution_step_row_counters.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0018_execution_run_reconciliation_events.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0019_forwardexecutionstep_query_parameters.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0020_execution_step_operation.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0021_add_fhrpgroup_nqe_map_choice.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0022_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0023_split_ip_address_maps.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0024_forwarddeviceanalysis.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0025_device_analysis_netboxmodel.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0026_forwarddeviceanalysis_collection_result.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0027_alter_forwardexecutionrun_backend.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0028_remove_forwardexecutionstep_run_and_more.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0029_forward_parent_device_cf.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0030_forwarddeviceanalysis_cve_ids.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/0031_encrypt_source_password.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/migrations/__init__.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/models.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/navigation.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/__init__.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_apic_cimc_inventory.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_apic_nodes.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_app_profiles.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_bridge_domains.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_command_inventory.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_contracts.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_endpoint_groups.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_fabrics.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_filters.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_l3outs.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_nodes.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_pods.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_static_port_bindings.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_tenants.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_aci_vrfs.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_bgp_address_families.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_bgp_peer_address_families.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_bgp_peers.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_device_analysis.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_device_feature_tags.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_device_feature_tags_with_rules.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_device_models.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_device_models_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_device_types.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_device_vendors.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_hsrp_groups.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_inferred_interface_cables.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_interfaces.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_inventory_items.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_ip_addresses_ipv4.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_ip_addresses_ipv6.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_ip_addresses_unassignable_diagnostics.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_locations.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_mac_addresses.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_modules.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_ospf_areas.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_ospf_instances.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_ospf_interfaces.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_peering_sessions.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_platforms.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_prefixes_ipv4.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_prefixes_ipv6.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_routing_import_diagnostics.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_virtual_chassis.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_vlans.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/forward_vrfs.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/queries/netbox_utilities.nqe +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/signals.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/static/forward_netbox/img/fn-logo.svg +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tables.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/template_content.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwarddriftpolicy.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwardingestion.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwardnqemap.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwardsource.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwardsync.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwardsync_dependency_preview.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwardsync_drift_report.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwardsync_health.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwardsync_module_readiness.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwardsync_scope_reconciliation.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/forwardvalidationrun.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/inc/brand_bar.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/inc/device_analysis_panel.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/inc/diff.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/inc/execution_insights.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/inc/logs_pending.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/inc/merge_form.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/inc/validation_force_allow_form.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/partials/change_explainability.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/partials/ingestion_all.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/partials/ingestion_merge_button.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/partials/ingestion_progress.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/partials/ingestion_statistics.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/partials/ingestion_status.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/partials/job_logs.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/partials/object_tabs.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/partials/stage_switcher_buttons.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templates/forward_netbox/partials/validation_force_allow_button.html +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templatetags/__init__.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/templatetags/forward_netbox_helpers.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/__init__.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/fixtures/aci_command_inventory_expected.json +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/fixtures/aci_discovery_expected.json +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/scenarios.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_api_usage.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_api_views.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_apic_cimc_readiness_audit_command.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_apply_engine.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_apply_identity_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_branching.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_brand_bar.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_bulk_adapter_parity.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_bulk_merge_scale.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_crypto.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_dependency_preview_summary.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_device_analysis_query.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_device_scope_reconciliation_audit_command.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_device_scope_tagging.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_execution_insights_delete_label.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_forms.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_forward_api.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_health.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_ingestion_merge.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_interface_naming.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_ip_address_dedup_query.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_ipaddress_dup_global.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_issue_rendering.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_job_compat.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_job_liveness.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_jobs.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_log_export.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_logging.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_migrations.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_models.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_module_readiness.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_module_readiness_rollup.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_plugin_integrations.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_primary_ip.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_primary_ip_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_primary_ip_integration.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_query_binding.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_query_diff_coverage_audit_command.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_query_registry.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_query_variants.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_release_readiness_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_runtime_dependency_check.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_scope_ipam_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_scope_module_ui.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_sensitive_content.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_stability_hardening.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_sync.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_sync_aci.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_sync_facade.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_sync_orchestration.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_sync_runner_contracts.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_sync_state.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_synthetic_scenarios.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_validation_org_query_audit_command.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_vsys_parent.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/urls.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/__init__.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/api_usage.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/apply_engine.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/apply_engine_bulk.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/apply_engine_decision.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/apply_identity_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/branch_budget.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/branching.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/change_explainability.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/crypto.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/density_learning.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/device_analysis.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/direct_changes.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/drift_report.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/execution_ledger.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/execution_telemetry.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/fast_bootstrap_executor.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/fetch_artifacts.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/forward_api.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/forward_api_impl.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/health_apply_fetch.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/health_checks.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/health_summary_blocks.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/ingestion_issues.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/ingestion_merge.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/ingestion_presentation.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/interface_naming.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/job_compat.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/job_liveness.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/json_safe.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/logging.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/merge.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/model_contracts.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/model_validation.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/module_readiness.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/multi_branch_lifecycle.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/plugin_integrations/__init__.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/plugin_integrations/registry.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/primary_ip.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/primary_ip_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/query_binding.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/query_diagnostics.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/query_fetch.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/query_registry.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/runtime_guidance.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/scope_ipam_audit.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/scope_reconciliation.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sensitive_content.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/single_branch_executor.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/snapshot_freshness.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/support_bundle_archive.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_aci.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_cable.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_contracts.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_core_models.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_device.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_events.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_execution.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_facade.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_interface.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_inventory_module.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_ipam.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_orchestration.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_primitives.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_reporting.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_routing.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_routing_impl.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_runner_adapters.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_runner_contracts.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/sync_state.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/validation.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/validation_org_query.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/vsys_parent.py +0 -0
- {forward_netbox-2.4.2 → forward_netbox-2.4.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.4.
|
|
3
|
+
Version: 2.4.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
|
|
@@ -99,7 +99,9 @@ Latest release requires NetBox `4.6.4` and `netbox-branching` `1.1.0+`. Expand f
|
|
|
99
99
|
|
|
100
100
|
| Plugin Release | NetBox Version | Status |
|
|
101
101
|
| --- | --- | --- |
|
|
102
|
-
| `v2.4.
|
|
102
|
+
| `v2.4.4` | `4.6.4` required; needs netbox-branching `1.1.0+` | Current release; Fix: SNMP-endpoint import now works on **tag-scoped** syncs — the device-tag include scope silently excluded every endpoint, both query-side and in the plugin's local scope filter (whose scoped-device set was built from modeled devices only, so endpoint rows were always dropped; with prune enabled they would even be deleted). Endpoint import now ignores the include scope (exclude tags still apply) and endpoint names join the scoped set (validated live: 355 Avocent endpoints import under a tag-scoped sync). Also fixes the merge-phase `Tag with this Name already exists` issues: a same-named/same-slug tag already on main is now treated as merged instead of failing the branch's tag create. |
|
|
103
|
+
| `v2.4.3` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.4.4`; Fix: the pinned-query opt-in Health warning (2.4.1) over-claimed failure — it reads "nothing new syncs" and fires on any pinned map with the feature on, even after the query is fixed, because the Health page can't read a pinned query's contents. Reworded to a "Pinned — can't verify locally" heads-up that points at **Export Live Query Drift** to confirm (`source_matches_bundled`), instead of asserting failure. No behavior change. |
|
|
104
|
+
| `v2.4.2` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.4.3`; Fix: endpoint import (`sync_endpoints`) and device-tag sync (`sync_device_tags`) now work with the alias-aware and rules-aware query variants (`forward_devices_with_netbox_aliases`, `forward_device_feature_tags_with_rules`), not just the base queries — operators running the variants saw the toggles silently do nothing (validated live: 355 Avocent endpoints import; `Mgmt_*` tags sync). Adds a **Publish Bundled Queries** button on the sync Health page (beside Refresh Query IDs) and two Health warnings: when an opt-in feature is enabled but no enabled map provides it, and when a base query and its opt-in variant are both enabled (they double-apply rows for the same model and churn — enable one). The alias-aware device query now emits the clean role name (e.g. `ROUTER`) to match the base query — expect a one-time role update on alias-mapped devices. |
|
|
103
105
|
| `v2.4.1` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.4.2`; Fix: opt-in features (SNMP endpoint import, device-tag sync) silently did nothing on sources that run org-managed **pinned** Forward query IDs predating the feature — the sync Health page now raises an actionable warning instead of a silent badge. Remediation: publish the bundled queries to your Forward org folder (Overwrite on), then use Refresh Query IDs, then re-sync. |
|
|
104
106
|
| `v2.4.0` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.4.1`; Fix: the "Import SNMP Endpoints as Devices" toggle now renders on the source form (the field shipped in 2.3.2 but was not in any fieldset, so it never showed), letting operators enable endpoint import from the GUI. |
|
|
105
107
|
| `v2.3.2` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.4.0`; Feature: optional import of Forward SNMP endpoints (e.g. Avocent console servers) as NetBox devices — off by default (`sync_endpoints`), enabled per source and scoped by the same device tags. |
|
|
@@ -66,7 +66,9 @@ Latest release requires NetBox `4.6.4` and `netbox-branching` `1.1.0+`. Expand f
|
|
|
66
66
|
|
|
67
67
|
| Plugin Release | NetBox Version | Status |
|
|
68
68
|
| --- | --- | --- |
|
|
69
|
-
| `v2.4.
|
|
69
|
+
| `v2.4.4` | `4.6.4` required; needs netbox-branching `1.1.0+` | Current release; Fix: SNMP-endpoint import now works on **tag-scoped** syncs — the device-tag include scope silently excluded every endpoint, both query-side and in the plugin's local scope filter (whose scoped-device set was built from modeled devices only, so endpoint rows were always dropped; with prune enabled they would even be deleted). Endpoint import now ignores the include scope (exclude tags still apply) and endpoint names join the scoped set (validated live: 355 Avocent endpoints import under a tag-scoped sync). Also fixes the merge-phase `Tag with this Name already exists` issues: a same-named/same-slug tag already on main is now treated as merged instead of failing the branch's tag create. |
|
|
70
|
+
| `v2.4.3` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.4.4`; Fix: the pinned-query opt-in Health warning (2.4.1) over-claimed failure — it reads "nothing new syncs" and fires on any pinned map with the feature on, even after the query is fixed, because the Health page can't read a pinned query's contents. Reworded to a "Pinned — can't verify locally" heads-up that points at **Export Live Query Drift** to confirm (`source_matches_bundled`), instead of asserting failure. No behavior change. |
|
|
71
|
+
| `v2.4.2` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.4.3`; Fix: endpoint import (`sync_endpoints`) and device-tag sync (`sync_device_tags`) now work with the alias-aware and rules-aware query variants (`forward_devices_with_netbox_aliases`, `forward_device_feature_tags_with_rules`), not just the base queries — operators running the variants saw the toggles silently do nothing (validated live: 355 Avocent endpoints import; `Mgmt_*` tags sync). Adds a **Publish Bundled Queries** button on the sync Health page (beside Refresh Query IDs) and two Health warnings: when an opt-in feature is enabled but no enabled map provides it, and when a base query and its opt-in variant are both enabled (they double-apply rows for the same model and churn — enable one). The alias-aware device query now emits the clean role name (e.g. `ROUTER`) to match the base query — expect a one-time role update on alias-mapped devices. |
|
|
70
72
|
| `v2.4.1` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.4.2`; Fix: opt-in features (SNMP endpoint import, device-tag sync) silently did nothing on sources that run org-managed **pinned** Forward query IDs predating the feature — the sync Health page now raises an actionable warning instead of a silent badge. Remediation: publish the bundled queries to your Forward org folder (Overwrite on), then use Refresh Query IDs, then re-sync. |
|
|
71
73
|
| `v2.4.0` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.4.1`; Fix: the "Import SNMP Endpoints as Devices" toggle now renders on the source form (the field shipped in 2.3.2 but was not in any fieldset, so it never showed), letting operators enable endpoint import from the GUI. |
|
|
72
74
|
| `v2.3.2` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.4.0`; Feature: optional import of Forward SNMP endpoints (e.g. Avocent console servers) as NetBox devices — off by default (`sync_endpoints`), enabled per source and scoped by the same device tags. |
|
|
@@ -8,7 +8,7 @@ class NetboxForwardConfig(PluginConfig):
|
|
|
8
8
|
name = "forward_netbox"
|
|
9
9
|
verbose_name = "Forward"
|
|
10
10
|
description = "Sync Forward data into NetBox using built-in NQE queries."
|
|
11
|
-
version = "2.4.
|
|
11
|
+
version = "2.4.4"
|
|
12
12
|
base_url = "forward"
|
|
13
13
|
min_version = "4.6.4"
|
|
14
14
|
|
|
@@ -44,15 +44,12 @@ foreach row in (
|
|
|
44
44
|
(
|
|
45
45
|
// SNMP endpoints (opt-in): generic SSH/SNMP devices Forward collects but
|
|
46
46
|
// does not model. Generic identity from MIB-2; Avocent overlay by enterprise
|
|
47
|
-
// OID 10418.
|
|
47
|
+
// OID 10418. Device-tag INCLUDE scope narrows modeled devices only —
|
|
48
|
+
// endpoints rarely carry device scoping tags, so requiring them silently
|
|
49
|
+
// excluded every endpoint on scoped syncs. Exclude tags still apply.
|
|
48
50
|
foreach endpoint in network.endpoints
|
|
49
51
|
where sync_endpoints
|
|
50
52
|
where !isEmpty(endpoint.snmpOutputs)
|
|
51
|
-
where isEmpty(device_tag_include_tags)
|
|
52
|
-
|| (device_tag_include_match == "all"
|
|
53
|
-
&& all(foreach tag in device_tag_include_tags select tag in endpoint.tagNames))
|
|
54
|
-
|| (device_tag_include_match != "all"
|
|
55
|
-
&& any(foreach tag in device_tag_include_tags select tag in endpoint.tagNames))
|
|
56
53
|
where isEmpty(device_tag_exclude_tags)
|
|
57
54
|
|| !any(foreach tag in device_tag_exclude_tags select tag in endpoint.tagNames)
|
|
58
55
|
let sysDescrOpt = max(foreach o in endpoint.snmpOutputs where o.requestedOid == "1.3.6.1.2.1.1.1" select max(foreach e in o.rawOidEntries select e.rawValue))
|
|
@@ -86,16 +86,13 @@ foreach row in (
|
|
|
86
86
|
(
|
|
87
87
|
// SNMP endpoints (opt-in): identical branch to forward_devices.nqe. Generic
|
|
88
88
|
// SSH/SNMP devices Forward collects but does not model. Generic identity from
|
|
89
|
-
// MIB-2; Avocent overlay by enterprise OID 10418.
|
|
90
|
-
//
|
|
89
|
+
// MIB-2; Avocent overlay by enterprise OID 10418. Device-tag INCLUDE scope
|
|
90
|
+
// narrows modeled devices only — endpoints rarely carry device scoping tags,
|
|
91
|
+
// so requiring them silently excluded every endpoint on scoped syncs.
|
|
92
|
+
// Exclude tags still apply.
|
|
91
93
|
foreach endpoint in network.endpoints
|
|
92
94
|
where sync_endpoints
|
|
93
95
|
where !isEmpty(endpoint.snmpOutputs)
|
|
94
|
-
where isEmpty(device_tag_include_tags)
|
|
95
|
-
|| (device_tag_include_match == "all"
|
|
96
|
-
&& all(foreach tag in device_tag_include_tags select tag in endpoint.tagNames))
|
|
97
|
-
|| (device_tag_include_match != "all"
|
|
98
|
-
&& any(foreach tag in device_tag_include_tags select tag in endpoint.tagNames))
|
|
99
96
|
where isEmpty(device_tag_exclude_tags)
|
|
100
97
|
|| !any(foreach tag in device_tag_exclude_tags select tag in endpoint.tagNames)
|
|
101
98
|
let sysDescrOpt = max(foreach o in endpoint.snmpOutputs where o.requestedOid == "1.3.6.1.2.1.1.1" select max(foreach e in o.rawOidEntries select e.rawValue))
|
|
@@ -569,3 +569,54 @@ class DeferredSelfRefFKSplitTest(TestCase):
|
|
|
569
569
|
self.assertIn(upd_key, to_process)
|
|
570
570
|
self.assertEqual(to_process[upd_key].postchange_data["lag"], 99)
|
|
571
571
|
self.assertIn(cc.key, to_process[upd_key].depends_on)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
class BulkMergeTagNameCollisionTest(TransactionTestCase):
|
|
575
|
+
"""A branch tag CREATE that collides by NAME with a main-side tag skips.
|
|
576
|
+
|
|
577
|
+
While a merge applies device UPDATEs (ordered before CREATEs),
|
|
578
|
+
netbox_branching sets device tags by name, get_or_creating the tag on main
|
|
579
|
+
with a new pk. The branch's tag CREATE then violated the unique name
|
|
580
|
+
constraint and surfaced as a ValidationError ingestion issue even though the
|
|
581
|
+
desired end state (tag exists) was already reached.
|
|
582
|
+
"""
|
|
583
|
+
|
|
584
|
+
def setUp(self):
|
|
585
|
+
from django.contrib.auth import get_user_model
|
|
586
|
+
|
|
587
|
+
self.user = get_user_model().objects.create_user(username="tag-merge-user")
|
|
588
|
+
self.request = RequestFactory().get(reverse("home"))
|
|
589
|
+
self.request.user = self.user
|
|
590
|
+
self.logger = logging.getLogger("forward_netbox.tests.bulk_merge")
|
|
591
|
+
|
|
592
|
+
def test_tag_create_name_collision_skips_instead_of_failing(self):
|
|
593
|
+
from extras.models import Tag
|
|
594
|
+
|
|
595
|
+
branch = provision_branch(user=self.user, name="Tag Collision")
|
|
596
|
+
with activate_branch(branch), event_tracking(self.request):
|
|
597
|
+
self.request.id = uuid.uuid4()
|
|
598
|
+
self.request.user = self.user
|
|
599
|
+
Tag.objects.create(name="Mgmt_Coll", slug="mgmt-coll")
|
|
600
|
+
|
|
601
|
+
# Simulate the mid-merge name-based get_or_create on main: same name,
|
|
602
|
+
# different pk than the branch row.
|
|
603
|
+
Tag.objects.create(name="Mgmt_Coll", slug="mgmt-coll")
|
|
604
|
+
|
|
605
|
+
changes = branch.get_unmerged_changes().order_by("time")
|
|
606
|
+
apply_one = Mock(return_value=False) # fallback would record an issue
|
|
607
|
+
applied, failed, _models = bulk_merge_changes(
|
|
608
|
+
branch,
|
|
609
|
+
changes,
|
|
610
|
+
self.request,
|
|
611
|
+
self.user,
|
|
612
|
+
self.logger,
|
|
613
|
+
apply_one=apply_one,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
self.assertEqual(failed, 0)
|
|
617
|
+
# The colliding create must be skipped in the batch path, never routed
|
|
618
|
+
# to the per-object fallback (which would raise the ValidationError).
|
|
619
|
+
for call in apply_one.call_args_list:
|
|
620
|
+
collapsed = call.args[0]
|
|
621
|
+
self.assertNotEqual(collapsed.key[0], "extras.tag")
|
|
622
|
+
self.assertEqual(Tag.objects.filter(name="Mgmt_Coll").count(), 1)
|
|
@@ -74,9 +74,13 @@ class OptInPinnedDriftElevationTest(SimpleTestCase):
|
|
|
74
74
|
self.assertEqual(drift[0]["severity"], "warn")
|
|
75
75
|
self.assertEqual(drift[0]["status"], "direct_query_id_optin_stale_risk")
|
|
76
76
|
self.assertIn("Refresh Query IDs", drift[0]["remediation"])
|
|
77
|
+
# Wording is a "can't verify locally" heads-up pointing at the live
|
|
78
|
+
# check, not a confirmed failure.
|
|
79
|
+
self.assertIn("Live Query Drift", drift[0]["remediation"])
|
|
80
|
+
self.assertIn("can't inspect locally", drift[0]["message"])
|
|
77
81
|
# Badge label must track the elevated status, not the stale build-time one.
|
|
78
82
|
self.assertNotEqual(drift[0].get("status_label"), "Org-managed (pinned)")
|
|
79
|
-
self.assertIn("
|
|
83
|
+
self.assertIn("verify", drift[0].get("status_label", ""))
|
|
80
84
|
|
|
81
85
|
def test_endpoints_disabled_leaves_pinned_device_query_silent(self):
|
|
82
86
|
drift = [self._pinned("forward_devices.nqe")]
|
|
@@ -131,3 +135,129 @@ class EndpointFormRenderTest(TestCase):
|
|
|
131
135
|
"sync_endpoints field exists but is not in any FieldSet, so the "
|
|
132
136
|
"form never renders the toggle.",
|
|
133
137
|
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
from forward_netbox.utilities.query_fetch_execution import ( # noqa: E402
|
|
141
|
+
ForwardQueryFetcher,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class EndpointScopeUnionTest(TestCase):
|
|
146
|
+
"""Tag-scoped syncs must keep opt-in endpoint rows, not silently drop them.
|
|
147
|
+
|
|
148
|
+
scoped_device_names was built from network.devices only, so the local scope
|
|
149
|
+
filter removed every endpoint row (and prune would delete them). With
|
|
150
|
+
sync_endpoints on, endpoint names join the scoped set; exclude tags still
|
|
151
|
+
apply to the endpoint probe.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
def _fetcher(self, client):
|
|
155
|
+
from unittest.mock import Mock
|
|
156
|
+
|
|
157
|
+
return ForwardQueryFetcher(Mock(), client, Mock())
|
|
158
|
+
|
|
159
|
+
def test_scope_union_includes_endpoint_names(self):
|
|
160
|
+
from unittest.mock import Mock
|
|
161
|
+
|
|
162
|
+
client = Mock()
|
|
163
|
+
client.run_nqe_query.side_effect = [
|
|
164
|
+
[{"name": "dev-1", "site": "dc1", "tagNames": ["ACI"]}], # device scope
|
|
165
|
+
[{"name": "avocent-1"}, {"name": "avocent-2"}], # endpoint probe
|
|
166
|
+
]
|
|
167
|
+
fetcher = self._fetcher(client)
|
|
168
|
+
names, _sites, _matched, failed = fetcher._resolve_scoped_tag_scope(
|
|
169
|
+
network_id="n",
|
|
170
|
+
snapshot_id="s",
|
|
171
|
+
include_tags=["ACI"],
|
|
172
|
+
exclude_tags=[],
|
|
173
|
+
include_match="any",
|
|
174
|
+
sync_endpoints=True,
|
|
175
|
+
)
|
|
176
|
+
self.assertEqual(names, {"dev-1", "avocent-1", "avocent-2"})
|
|
177
|
+
self.assertFalse(failed)
|
|
178
|
+
|
|
179
|
+
def test_scope_without_endpoints_flag_is_unchanged(self):
|
|
180
|
+
from unittest.mock import Mock
|
|
181
|
+
|
|
182
|
+
client = Mock()
|
|
183
|
+
client.run_nqe_query.return_value = [
|
|
184
|
+
{"name": "dev-1", "site": "dc1", "tagNames": ["ACI"]}
|
|
185
|
+
]
|
|
186
|
+
fetcher = self._fetcher(client)
|
|
187
|
+
names, _sites, _matched, failed = fetcher._resolve_scoped_tag_scope(
|
|
188
|
+
network_id="n",
|
|
189
|
+
snapshot_id="s",
|
|
190
|
+
include_tags=["ACI"],
|
|
191
|
+
exclude_tags=[],
|
|
192
|
+
include_match="any",
|
|
193
|
+
)
|
|
194
|
+
self.assertEqual(names, {"dev-1"})
|
|
195
|
+
self.assertFalse(failed)
|
|
196
|
+
client.run_nqe_query.assert_called_once()
|
|
197
|
+
|
|
198
|
+
def test_endpoint_probe_honors_exclude_tags(self):
|
|
199
|
+
from unittest.mock import Mock
|
|
200
|
+
|
|
201
|
+
client = Mock()
|
|
202
|
+
client.run_nqe_query.return_value = [{"name": "avocent-1"}]
|
|
203
|
+
fetcher = self._fetcher(client)
|
|
204
|
+
fetcher._resolve_scoped_endpoint_names(
|
|
205
|
+
network_id="n", snapshot_id="s", exclude_tags=["Decom"]
|
|
206
|
+
)
|
|
207
|
+
query = client.run_nqe_query.call_args.kwargs["query"]
|
|
208
|
+
self.assertIn("network.endpoints", query)
|
|
209
|
+
self.assertIn('"Decom" in endpoint.tagNames', query)
|
|
210
|
+
|
|
211
|
+
def test_endpoint_probe_failure_warns_and_returns_none(self):
|
|
212
|
+
from unittest.mock import Mock
|
|
213
|
+
|
|
214
|
+
from forward_netbox.exceptions import ForwardClientError
|
|
215
|
+
|
|
216
|
+
client = Mock()
|
|
217
|
+
client.run_nqe_query.side_effect = ForwardClientError("boom")
|
|
218
|
+
fetcher = self._fetcher(client)
|
|
219
|
+
names = fetcher._resolve_scoped_endpoint_names(
|
|
220
|
+
network_id="n", snapshot_id="s", exclude_tags=[]
|
|
221
|
+
)
|
|
222
|
+
self.assertIsNone(names)
|
|
223
|
+
fetcher.logger.log_warning.assert_called_once()
|
|
224
|
+
|
|
225
|
+
def test_endpoint_probe_failure_flags_scope_as_failed(self):
|
|
226
|
+
# If the probe fails but the query still emitted endpoint rows, the
|
|
227
|
+
# local filter would drop them — and prune would DELETE previously
|
|
228
|
+
# imported endpoints. The failure flag lets resolve_context disable
|
|
229
|
+
# endpoint emission for the run instead.
|
|
230
|
+
from unittest.mock import Mock
|
|
231
|
+
|
|
232
|
+
from forward_netbox.exceptions import ForwardClientError
|
|
233
|
+
|
|
234
|
+
client = Mock()
|
|
235
|
+
client.run_nqe_query.side_effect = [
|
|
236
|
+
[{"name": "dev-1", "site": "dc1", "tagNames": ["ACI"]}],
|
|
237
|
+
ForwardClientError("boom"),
|
|
238
|
+
]
|
|
239
|
+
fetcher = self._fetcher(client)
|
|
240
|
+
names, _sites, _matched, failed = fetcher._resolve_scoped_tag_scope(
|
|
241
|
+
network_id="n",
|
|
242
|
+
snapshot_id="s",
|
|
243
|
+
include_tags=["ACI"],
|
|
244
|
+
exclude_tags=[],
|
|
245
|
+
include_match="any",
|
|
246
|
+
sync_endpoints=True,
|
|
247
|
+
)
|
|
248
|
+
self.assertEqual(names, {"dev-1"})
|
|
249
|
+
self.assertTrue(failed)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class EndpointBranchIncludeScopeRemovalTest(SimpleTestCase):
|
|
253
|
+
"""The endpoint branch must not gate on device INCLUDE tags (exclude only)."""
|
|
254
|
+
|
|
255
|
+
def test_endpoint_branches_do_not_filter_include_tags(self):
|
|
256
|
+
for filename in (
|
|
257
|
+
"forward_devices.nqe",
|
|
258
|
+
"forward_devices_with_netbox_aliases.nqe",
|
|
259
|
+
):
|
|
260
|
+
src = _read_query(filename)
|
|
261
|
+
endpoint_branch = src.split("network.endpoints", 1)[1]
|
|
262
|
+
self.assertIn("device_tag_exclude_tags", endpoint_branch, filename)
|
|
263
|
+
self.assertNotIn("device_tag_include_tags", endpoint_branch, filename)
|
{forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/tests/test_scope_matched_tags.py
RENAMED
|
@@ -41,7 +41,9 @@ class ScopeMatchedTagsResolveTest(TestCase):
|
|
|
41
41
|
{"name": "d3", "site": "s", "tagNames": ["TagA", "TagB"]},
|
|
42
42
|
{"name": "d4", "site": "s", "tagNames": ["TagX"]},
|
|
43
43
|
]
|
|
44
|
-
names, sites, matched = self._fetcher(
|
|
44
|
+
names, sites, matched, _failed = self._fetcher(
|
|
45
|
+
client
|
|
46
|
+
)._resolve_scoped_tag_scope(
|
|
45
47
|
network_id="net-1",
|
|
46
48
|
snapshot_id="snap",
|
|
47
49
|
include_tags=["TagA", "TagB"],
|
|
@@ -65,7 +67,7 @@ class ScopeMatchedTagsResolveTest(TestCase):
|
|
|
65
67
|
exclude_tags=[],
|
|
66
68
|
include_match="any",
|
|
67
69
|
)
|
|
68
|
-
self.assertEqual(result, (set(), set(), {}))
|
|
70
|
+
self.assertEqual(result, (set(), set(), {}, False))
|
|
69
71
|
|
|
70
72
|
def test_context_as_dict_carries_full_matched_map(self):
|
|
71
73
|
ctx = ForwardQueryContext(
|
|
@@ -28,6 +28,7 @@ from collections import defaultdict
|
|
|
28
28
|
from django.db import DEFAULT_DB_ALIAS
|
|
29
29
|
from django.db import IntegrityError
|
|
30
30
|
from django.db import transaction
|
|
31
|
+
from django.db.models import Q
|
|
31
32
|
from mptt.models import MPTTModel
|
|
32
33
|
from netbox_branching.merge_strategies.squash import ActionType
|
|
33
34
|
from netbox_branching.merge_strategies.squash import CollapsedChange
|
|
@@ -398,6 +399,31 @@ def bulk_merge_changes(
|
|
|
398
399
|
existing = set(
|
|
399
400
|
model_class.objects.filter(pk__in=all_pks).values_list("pk", flat=True)
|
|
400
401
|
)
|
|
402
|
+
# Tags collide by NAME, not pk: while the merge applies device UPDATEs
|
|
403
|
+
# (ordered before CREATEs), netbox_branching sets device tags by name,
|
|
404
|
+
# which get_or_creates the tag on main with a NEW pk. The branch's tag
|
|
405
|
+
# CREATE then violates the unique name/slug constraint. Treat a
|
|
406
|
+
# same-named (or same-slug) main-side tag as already merged. If the
|
|
407
|
+
# branch create carried different non-unique attrs (color/description),
|
|
408
|
+
# they converge on the next sync: its branch is provisioned from a main
|
|
409
|
+
# that has the tag, so the apply-time coalesce records an UPDATE.
|
|
410
|
+
existing_names: set[str] = set()
|
|
411
|
+
existing_slugs: set[str] = set()
|
|
412
|
+
if getattr(model_class._meta, "label_lower", "") == "extras.tag":
|
|
413
|
+
pending_names = [
|
|
414
|
+
str((c.postchange_data or {}).get("name") or "") for c in pending
|
|
415
|
+
]
|
|
416
|
+
pending_slugs = [
|
|
417
|
+
str((c.postchange_data or {}).get("slug") or "") for c in pending
|
|
418
|
+
]
|
|
419
|
+
name_filter = Q(name__in=[n for n in pending_names if n]) | Q(
|
|
420
|
+
slug__in=[s for s in pending_slugs if s]
|
|
421
|
+
)
|
|
422
|
+
for name, slug in model_class.objects.filter(name_filter).values_list(
|
|
423
|
+
"name", "slug"
|
|
424
|
+
):
|
|
425
|
+
existing_names.add(str(name))
|
|
426
|
+
existing_slugs.add(str(slug))
|
|
401
427
|
skipped = 0
|
|
402
428
|
objects = []
|
|
403
429
|
built = [] # (collapsed, deserialized) for rows we actually create
|
|
@@ -405,6 +431,14 @@ def bulk_merge_changes(
|
|
|
405
431
|
if collapsed_change.key[1] in existing:
|
|
406
432
|
skipped += 1
|
|
407
433
|
continue
|
|
434
|
+
if existing_names or existing_slugs:
|
|
435
|
+
data = collapsed_change.postchange_data or {}
|
|
436
|
+
if (
|
|
437
|
+
str(data.get("name") or "") in existing_names
|
|
438
|
+
or str(data.get("slug") or "") in existing_slugs
|
|
439
|
+
):
|
|
440
|
+
skipped += 1
|
|
441
|
+
continue
|
|
408
442
|
try:
|
|
409
443
|
deserialized = _deserialize(
|
|
410
444
|
model_class, collapsed_change, change_logger
|
|
@@ -136,17 +136,23 @@ def _elevate_optin_pinned_query_drift(query_drift, source_parameters):
|
|
|
136
136
|
# stale "Org-managed (pinned)" badge on the elevated row.
|
|
137
137
|
item["status_label"] = _QUERY_DRIFT_STATUS_LABELS.get(
|
|
138
138
|
"direct_query_id_optin_stale_risk",
|
|
139
|
-
"Pinned —
|
|
139
|
+
"Pinned — can't verify locally",
|
|
140
140
|
)
|
|
141
141
|
item["severity"] = "warn"
|
|
142
|
+
# This is a "can't verify locally" heads-up, not a confirmed failure:
|
|
143
|
+
# the Health page can't read a pinned query's contents, so it warns for
|
|
144
|
+
# any pinned map with the feature on — including one that's already
|
|
145
|
+
# current after publishing. Point at the live check, don't claim failure.
|
|
142
146
|
item["message"] = (
|
|
143
|
-
f"“{label}” is enabled
|
|
144
|
-
"
|
|
145
|
-
"
|
|
147
|
+
f"“{label}” is enabled and this map runs a pinned Forward query ID, "
|
|
148
|
+
"which the Health page can't inspect locally to confirm it includes "
|
|
149
|
+
"the feature. If those rows aren't importing, the pinned query may "
|
|
150
|
+
"predate the feature."
|
|
146
151
|
)
|
|
147
152
|
item["remediation"] = (
|
|
148
|
-
"
|
|
149
|
-
"
|
|
153
|
+
"Use Export Live Query Drift on this page to confirm the pinned "
|
|
154
|
+
"query matches the current bundled source. If it doesn't, use "
|
|
155
|
+
"Publish Bundled Queries, then Refresh Query IDs, then re-run the "
|
|
150
156
|
"sync."
|
|
151
157
|
)
|
|
152
158
|
item["remediation_action"] = "refresh_query_ids"
|
{forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/query_binding_resolution.py
RENAMED
|
@@ -436,7 +436,7 @@ def _committed_query_source(committed_query: dict) -> str:
|
|
|
436
436
|
# read like an error.
|
|
437
437
|
_QUERY_DRIFT_STATUS_LABELS = {
|
|
438
438
|
"direct_query_id_unverified": "Org-managed (pinned)",
|
|
439
|
-
"direct_query_id_optin_stale_risk": "Pinned —
|
|
439
|
+
"direct_query_id_optin_stale_risk": "Pinned — can't verify locally",
|
|
440
440
|
}
|
|
441
441
|
|
|
442
442
|
|
{forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/utilities/query_fetch_execution.py
RENAMED
|
@@ -392,6 +392,7 @@ class ForwardQueryFetcher:
|
|
|
392
392
|
tuple(exclude_tags),
|
|
393
393
|
include_match,
|
|
394
394
|
prune_out_of_scope,
|
|
395
|
+
sync_endpoints,
|
|
395
396
|
branch_run_state.get("ingestion_id"),
|
|
396
397
|
branch_run_state.get("pending_ingestion_id"),
|
|
397
398
|
branch_run_state.get("current_ingestion_id"),
|
|
@@ -406,6 +407,7 @@ class ForwardQueryFetcher:
|
|
|
406
407
|
include_tags=include_tags,
|
|
407
408
|
exclude_tags=exclude_tags,
|
|
408
409
|
include_match=include_match,
|
|
410
|
+
sync_endpoints=sync_endpoints,
|
|
409
411
|
)
|
|
410
412
|
cached_context = self._load_context_artifact(context_artifact)
|
|
411
413
|
if cached_context is None:
|
|
@@ -428,21 +430,30 @@ class ForwardQueryFetcher:
|
|
|
428
430
|
scoped_device_names,
|
|
429
431
|
scoped_site_names,
|
|
430
432
|
scoped_matched_tags,
|
|
433
|
+
endpoint_scope_failed,
|
|
431
434
|
) = self._resolve_scoped_tag_scope(
|
|
432
435
|
network_id=network_id,
|
|
433
436
|
snapshot_id=snapshot_id,
|
|
434
437
|
include_tags=include_tags,
|
|
435
438
|
exclude_tags=exclude_tags,
|
|
436
439
|
include_match=include_match,
|
|
440
|
+
sync_endpoints=sync_endpoints,
|
|
437
441
|
)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
442
|
+
if endpoint_scope_failed:
|
|
443
|
+
# The scoped set carries no endpoint names, so emitting endpoint
|
|
444
|
+
# rows would get them dropped locally — and pruned as deletes.
|
|
445
|
+
# Disable endpoint emission for this run and do NOT persist the
|
|
446
|
+
# endpoint-less scope under the endpoints-on artifact key.
|
|
447
|
+
sync_endpoints = False
|
|
448
|
+
else:
|
|
449
|
+
self._save_context_artifact(
|
|
450
|
+
context_artifact,
|
|
451
|
+
snapshot_info=snapshot_info,
|
|
452
|
+
snapshot_metrics=snapshot_metrics,
|
|
453
|
+
scoped_device_names=scoped_device_names,
|
|
454
|
+
scoped_site_names=scoped_site_names,
|
|
455
|
+
scoped_matched_tags=scoped_matched_tags,
|
|
456
|
+
)
|
|
446
457
|
else:
|
|
447
458
|
snapshot_info = dict(cached_context.get("snapshot_info") or {})
|
|
448
459
|
snapshot_metrics = dict(cached_context.get("snapshot_metrics") or {})
|
|
@@ -483,13 +494,16 @@ class ForwardQueryFetcher:
|
|
|
483
494
|
include_tags: list[str],
|
|
484
495
|
exclude_tags: list[str],
|
|
485
496
|
include_match: str,
|
|
497
|
+
sync_endpoints: bool = False,
|
|
486
498
|
) -> dict[str, Any] | None:
|
|
487
499
|
run = active_execution_run(self.sync)
|
|
488
500
|
if run is None:
|
|
489
501
|
return None
|
|
490
502
|
artifact_run_id = f"shared-sync-{getattr(self.sync, 'pk', 'unknown')}"
|
|
491
503
|
payload = {
|
|
492
|
-
|
|
504
|
+
# v3: scoped_device_names now varies with the endpoint-import
|
|
505
|
+
# toggle, so the cache key must too.
|
|
506
|
+
"version": 3,
|
|
493
507
|
"artifact_scope": "query_context",
|
|
494
508
|
"cache_scope": "shared_sync",
|
|
495
509
|
"sync_id": getattr(self.sync, "pk", None),
|
|
@@ -499,6 +513,7 @@ class ForwardQueryFetcher:
|
|
|
499
513
|
"device_tag_include": sorted(include_tags or []),
|
|
500
514
|
"device_tag_exclude": sorted(exclude_tags or []),
|
|
501
515
|
"device_tag_include_match": include_match or "any",
|
|
516
|
+
"sync_endpoints": bool(sync_endpoints),
|
|
502
517
|
}
|
|
503
518
|
return {
|
|
504
519
|
"key": fetch_artifact_key(payload),
|
|
@@ -575,9 +590,10 @@ class ForwardQueryFetcher:
|
|
|
575
590
|
include_tags: list[str],
|
|
576
591
|
exclude_tags: list[str],
|
|
577
592
|
include_match: str,
|
|
578
|
-
|
|
593
|
+
sync_endpoints: bool = False,
|
|
594
|
+
) -> tuple[set[str], set[str], dict[str, list[str]], bool]:
|
|
579
595
|
if not include_tags and not exclude_tags:
|
|
580
|
-
return set(), set(), {}
|
|
596
|
+
return set(), set(), {}, False
|
|
581
597
|
scope_where = build_device_tag_scope_where(
|
|
582
598
|
include_tags, exclude_tags, include_match
|
|
583
599
|
)
|
|
@@ -656,7 +672,77 @@ class ForwardQueryFetcher:
|
|
|
656
672
|
exclude_tags=exclude_tags,
|
|
657
673
|
include_match=include_match,
|
|
658
674
|
)
|
|
659
|
-
|
|
675
|
+
endpoint_scope_failed = False
|
|
676
|
+
if sync_endpoints:
|
|
677
|
+
endpoint_names = self._resolve_scoped_endpoint_names(
|
|
678
|
+
network_id=network_id,
|
|
679
|
+
snapshot_id=snapshot_id,
|
|
680
|
+
exclude_tags=exclude_tags,
|
|
681
|
+
)
|
|
682
|
+
if endpoint_names is None:
|
|
683
|
+
endpoint_scope_failed = True
|
|
684
|
+
else:
|
|
685
|
+
names |= endpoint_names
|
|
686
|
+
return names, sites, matched_tags_by_device, endpoint_scope_failed
|
|
687
|
+
|
|
688
|
+
def _resolve_scoped_endpoint_names(
|
|
689
|
+
self,
|
|
690
|
+
*,
|
|
691
|
+
network_id: str,
|
|
692
|
+
snapshot_id: str,
|
|
693
|
+
exclude_tags: list[str],
|
|
694
|
+
) -> set[str] | None:
|
|
695
|
+
"""Names of SNMP endpoints that opt-in endpoint import will emit.
|
|
696
|
+
|
|
697
|
+
Device-tag include scope narrows the modeled-device universe; SNMP
|
|
698
|
+
endpoints (e.g. Avocent console servers) rarely carry those device
|
|
699
|
+
scoping tags, so requiring them silently excludes every endpoint. When
|
|
700
|
+
endpoint import is enabled the endpoint names are unioned into the
|
|
701
|
+
scoped-device set so the local scope filter (and out-of-scope prune)
|
|
702
|
+
keeps their rows. Exclude tags still apply as the safety valve.
|
|
703
|
+
|
|
704
|
+
Returns ``None`` when the probe fails. The caller must then disable
|
|
705
|
+
endpoint emission for the run entirely (not just proceed with an empty
|
|
706
|
+
set): the device query would still emit endpoint rows, the local scope
|
|
707
|
+
filter would drop them, and with prune-out-of-scope enabled the dropped
|
|
708
|
+
rows would be emitted as DELETES of previously imported endpoints.
|
|
709
|
+
"""
|
|
710
|
+
where = ["where !isEmpty(endpoint.snmpOutputs)"]
|
|
711
|
+
for tag in exclude_tags:
|
|
712
|
+
where.append(f"where !({_nqe_string_literal(tag)} in endpoint.tagNames)")
|
|
713
|
+
query = "\n".join(
|
|
714
|
+
[
|
|
715
|
+
"foreach endpoint in network.endpoints",
|
|
716
|
+
*where,
|
|
717
|
+
"select { name: endpoint.name }",
|
|
718
|
+
]
|
|
719
|
+
)
|
|
720
|
+
try:
|
|
721
|
+
rows = self.client.run_nqe_query(
|
|
722
|
+
query=query,
|
|
723
|
+
network_id=network_id,
|
|
724
|
+
snapshot_id=snapshot_id,
|
|
725
|
+
fetch_all=True,
|
|
726
|
+
)
|
|
727
|
+
except (ForwardClientError, ForwardConnectivityError, ForwardQueryError) as exc:
|
|
728
|
+
self.logger.log_warning(
|
|
729
|
+
"Forward endpoint scope probe failed; SNMP endpoint import is "
|
|
730
|
+
f"disabled for this run: {_safe_exception_summary(exc)}",
|
|
731
|
+
obj=self.sync,
|
|
732
|
+
)
|
|
733
|
+
return None
|
|
734
|
+
endpoint_names = {
|
|
735
|
+
str(row.get("name") or "").strip()
|
|
736
|
+
for row in rows
|
|
737
|
+
if str(row.get("name") or "").strip()
|
|
738
|
+
}
|
|
739
|
+
if endpoint_names:
|
|
740
|
+
self.logger.log_info(
|
|
741
|
+
f"Added {len(endpoint_names)} SNMP endpoint(s) to the device tag "
|
|
742
|
+
"scope for opt-in endpoint import.",
|
|
743
|
+
obj=self.sync,
|
|
744
|
+
)
|
|
745
|
+
return endpoint_names
|
|
660
746
|
|
|
661
747
|
def _warn_if_scope_all_backfilled(
|
|
662
748
|
self,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{forward_netbox-2.4.2 → forward_netbox-2.4.4}/forward_netbox/management/commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|