Skip to content

Latest commit

 

History

History
340 lines (270 loc) · 16.2 KB

File metadata and controls

340 lines (270 loc) · 16.2 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

inventory_monitor_scripts is a Python package that collects network device inventory data via SNMP and uploads it to NetBox using the inventory-monitor-plugin. It supports multiple vendor platforms (Cisco, Juniper, Nokia) and SNMP versions (v2c, v3).

Development Commands

Installation & Setup

# Install in development mode with dependencies
pip install -e .

# Install system dependencies (required for easysnmp)
# macOS
brew install net-snmp

# Ubuntu/Debian
sudo apt-get install libsnmp-dev

# RHEL/CentOS
sudo yum install net-snmp-devel

Running the Application

# Process all devices matching NetBox filters
python -m inventory_monitor.main

# Process a specific device
python -m inventory_monitor.main --device devicename

# Use a custom environment file (e.g., production, staging, dev)
python -m inventory_monitor.main --env-file .env.production
python -m inventory_monitor.main --env-file .env.staging --device test-router

# Override SNMP credentials via CLI
python -m inventory_monitor.main --snmp-version 2 --snmp-community my_secret
python -m inventory_monitor.main --snmp-user admin --snmp-auth Pass --snmp-auth-method SHA

# Adjust SNMP connection tuning
python -m inventory_monitor.main --snmp-timeout 30 --snmp-retries 3

# Override environment variables at runtime
SNMP_VERSION='2' NETBOX_TAGS='production' python -m inventory_monitor.main

# Set log level
LOG_LEVEL=DEBUG python -m inventory_monitor.main

# Dump raw SNMP data as JSON for debugging
python -m inventory_monitor.main --device devicename --debug-dump debug_output.json

# Override NetBox settings via CLI
python -m inventory_monitor.main --netbox-url https://netbox.example.com/ --netbox-api-token mytoken

# Filter by tags via CLI
python -m inventory_monitor.main --netbox-tags production,core --netbox-tags-exclude decommissioned

# Adjust parallel jobs
python -m inventory_monitor.main --parallel-jobs 10

Logging

  • Logs are stored in the logs/ directory
  • logs/all.log - All messages at configured level (rotates at 10 MB)
  • logs/errors.log - Only error/critical messages with detailed traces (rotates at 10 MB)
  • Retention policy: 1 week
  • Log level configured via LOG_LEVEL environment variable (default: INFO)

Testing

# Run all tests
pytest tests/ -v

# Install test dependencies
pip install -e ".[test]"

Test Structure

  • tests/conftest.py — Mocks the easysnmp C-extension so tests run without net-snmp system libraries
  • tests/test_snmp.py — SNMP handler tests (serial validation, handler factory, human-readable conversion)
  • tests/test_models.py — Config dataclass and env-loading tests
  • tests/test_netbox.py — Probe diff, timestamps, and param preparation tests
  • tests/test_invmon.py — Inventory priority merging and device processing tests
  • tests/test_helper.pystripper() utility tests

The conftest.py mock only activates when easysnmp is not importable (i.e., net-snmp is not installed). When the real library is present, tests use it directly.

Architecture

Configuration Layer

Configuration uses frozen dataclasses defined in inventory_monitor/models.py:

AppConfig
├── snmp: SnmpConfig
│   ├── versions: List[int]       # e.g. [3, 2]
│   ├── v2: SnmpV2Config
│   │   └── community: str
│   ├── v3: SnmpV3Config
│   │   ├── user, auth_password, auth_protocol
│   │   ├── privacy_password, privacy_protocol
│   │   └── security_level
│   ├── timeout: int
│   └── retries: int
├── netbox: NetBoxConfig
│   ├── api_token, instance_url
│   ├── tags, tags_exclude
│   └── verify_ssl: bool
├── parallel_jobs: int
├── log_level: str
└── model_name_excludes: List[str]
  • Frozen dataclasses are immutable and picklable (safe for joblib multiprocessing)
  • load_config_from_env() builds AppConfig from .env -> os.environ
  • CLI overrides are merged via dataclasses.replace() in _build_config() in main.py

Dependency Injection

All core classes accept their config through constructors:

  • InventoryMonitor(config: AppConfig) — SNMP + model excludes
  • Netbox(config: NetBoxConfig) — API token, URL, SSL
  • ISnmp(snmp_config: SnmpConfig) — credentials, timeout, retries

All constructors default to None, falling back to load_config_from_env() if not provided. This allows gradual migration and standalone usage.

Core Pipeline

The application follows a three-stage pipeline defined in inventory_monitor/main.py:

  1. Device Discovery & Collection (collect_device_inventory()):

    • Queries NetBox API with filters (active status, primary IP, optional tags)
    • Filters built from config.netbox.tags and config.netbox.tags_exclude
    • Processes devices in parallel using joblib (configurable via config.parallel_jobs, default: 50)
    • Single devices are processed sequentially (no parallelization)
  2. Inventory Preparation (InventoryMonitor.prepare_device_sn_inventory()):

    • Merges inventory items by serial number
    • Prioritizes items based on device class (e.g., chassis > port > module > sensor)
    • Filters out excluded models (from config.model_name_excludes)
  3. NetBox Upload (process_device_inventory()):

    • Creates or updates Probes in NetBox via the inventory-monitor-plugin
    • Only processes devices with successful SNMP inventory retrieval

Module Responsibilities

models.py: Configuration dataclasses and environment loading

  • Frozen dataclasses: SnmpV2Config, SnmpV3Config, SnmpConfig, NetBoxConfig, AppConfig
  • load_config_from_env(env=None, env_file=".env"): Builds AppConfig from dotenv files and os.environ
    • env_file parameter allows loading from custom files (e.g., .env.production)
    • Default is .env for backward compatibility
  • Accepts both legacy entPhysicalModelName_exludes and corrected entPhysicalModelName_excludes spelling

snmp/: SNMP protocol handlers (subpackage)

  • snmp/constants.py: OID dicts and named field mappings (CISCO_ENTITY_FIELD, SROS_CHASSIS_FIELD, JUNOS_COMPONENT_FIELD, etc.)
  • snmp/errors.py: Credential sanitization for SNMP error messages
  • snmp/base.py: ISnmp abstract base class
    • Accepts SnmpConfig in constructor, defines interface with three methods:
      • get_snmp_connection(): Establish connection
      • get_snmp_inventory(): Retrieve raw SNMP data
      • get_human_readable_inventory(): Parse into standardized format
    • snmp_session() is an instance method reading self.snmp_config for credentials, timeout, retries
    • Retry loop: retries up to snmp_config.retries times per SNMP version before moving to next
    • Serial number validation: must be string, >=4 chars, not "BUILTIN"
  • snmp/cisco.py: SnmpCisco (Entity MIB), SnmpCiscoLegacy (older OID structure)
  • snmp/junos.py: SnmpJunos — Juniper JunOS devices
  • snmp/sros.py: SnmpSros — Nokia SROS devices
  • snmp/__init__.py: Re-exports all public names, PLATFORM_HANDLERS registry, get_snmp_handler() factory

netbox.py: NetBox API integration

  • Netbox(config: NetBoxConfig): Accepts config for token, URL, SSL verify
  • create_or_update_probe(): Creates or updates probe records
  • get_latest_probe_by_serial(): Retrieves existing probes for comparison
  • _check_probe_item_diff(): Detects if probe data has changed

invmon.py: Business logic orchestration

  • InventoryMonitor(config: AppConfig): Stores config for SNMP and model excludes
  • process_device(): Instance method — main device processing
    • Validates device has primary IPv4 address
    • Gets appropriate SNMP handler via get_snmp_handler(), passing self.config.snmp
    • Tries standard SNMP first, falls back to legacy for Cisco devices
    • Returns tuple of (nb_device, result_dict)
  • prepare_device_sn_inventory(): Instance method — serial-number based merging with class prioritization, reads self.config.model_name_excludes

logger.py: Logging configuration

  • Uses loguru for structured logging
  • Console output with color codes and function/line info
  • File rotation at 10 MB with 1-week retention
  • Separate error log for diagnostics

helper.py: Utility functions

  • stripper(): Safely strips whitespace from string values

main.py: CLI entry point and orchestration

  • Click CLI with --env-file, --device, SNMP override, NetBox override, and --parallel-jobs options
  • CliOverrides dataclass: Holds all CLI option values including env_file
  • _build_config(overrides): Loads AppConfig from env file (default .env or custom via --env-file), merges non-None CLI args via dataclasses.replace() (SNMP, NetBox, and top-level overrides)
  • --debug-dump flag: optionally dumps raw SNMP results as JSON
  • NetBox API errors (RequestError) are caught gracefully with logging (handles 500, invalid tags 400, etc.)
  • NetBox filter logic built inline from config.netbox.tags / config.netbox.tags_exclude

Configuration

Required environment variables (from .env file):

  • NETBOX_API_TOKEN: NetBox API authentication
  • NETBOX_INSTANCE_URL: NetBox instance URL (e.g., https://netbox.example.com/)
  • SNMP_VERSION: Comma-separated versions to try (e.g., "3,2")
  • SNMP_COMMUNITY: SNMPv2 community string
  • SNMP_USER, SNMP_AUTH, SNMP_AUTH_METHOD, SNMP_PRIVACY, SNMP_PRIVACY_METHOD, SNMP_SECURITY: SNMPv3 credentials

Optional environment variables:

  • NETBOX_TAGS: Comma-separated tags; only process devices with these tags
  • NETBOX_TAGS__N: Comma-separated tags; exclude devices with these tags
  • entPhysicalModelName_excludes: Comma-separated model names to exclude (legacy spelling entPhysicalModelName_exludes also accepted)
  • SNMP_TIMEOUT: SNMP timeout in seconds (default: 1)
  • SNMP_RETRIES: Number of retries per SNMP version before moving to next (default: 1)
  • NETBOX_VERIFY_SSL: Set to "false" to skip SSL certificate verification (default: true)
  • LOG_LEVEL: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL; default: INFO)
  • PARALLEL_JOBS: Number of parallel jobs for device processing (default: 50)

See .env.sample for complete example.

CLI Options

All SNMP parameters can be overridden from the command line (defaults to None, meaning use env value).

SECURITY NOTE: Command-line arguments for sensitive credentials (--snmp-community, --snmp-auth, --snmp-privacy, --netbox-api-token) are visible in process listings (ps aux) and shell history. Use --env-file with separate environment files for production deployments.

Option Description
--env-file Path to environment file (default: .env)
-d, --device Process a specific device by name
--snmp-version Comma-separated SNMP versions (e.g. "3,2")
--snmp-community SNMPv2 community string ⚠️ Use SNMP_COMMUNITY env var instead
--snmp-user SNMPv3 username
--snmp-auth SNMPv3 auth password ⚠️ Use SNMP_AUTH env var instead
--snmp-auth-method SNMPv3 auth protocol (MD5/SHA)
--snmp-privacy SNMPv3 privacy password ⚠️ Use SNMP_PRIVACY env var instead
--snmp-privacy-method SNMPv3 privacy protocol (DES/AES)
--snmp-security SNMPv3 security level
--snmp-timeout SNMP timeout in seconds (default: 1)
--snmp-retries Number of retries per SNMP version (default: 1)
--netbox-api-token NetBox API token ⚠️ Use NETBOX_API_TOKEN env var instead
--netbox-url NetBox instance URL
--netbox-tags Comma-separated tags to filter devices
--netbox-tags-exclude Comma-separated tags to exclude devices
--parallel-jobs Number of parallel processing jobs (int)
--log-level Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
--model-excludes Comma-separated model names to exclude from inventory
--debug-dump Dump raw SNMP data to JSON file

Key Implementation Details

Device Class Prioritization

Inventory items with same serial number are merged with priority (low to high):

  • backplane, container, fan(s), other, powerSupply(-ies), sensor(s), "-" (unrecognized)
  • module, port, chassis (highest priority - these override duplicates)

SNMP Connection Strategy

  1. Attempts each version listed in snmp_config.versions in order (default: [3, 2])
  2. For each version, retries up to snmp_config.retries times before moving to next
  3. SNMPv3 tests connectivity with a get(SYS_DESCR) before attempting bulkwalk
  4. Falls back to SNMPv2 with community string if v3 fails
  5. For Cisco devices, if standard Entity MIB returns empty, tries legacy OID structure
  6. Connection failures are logged with specific error reasons (auth failure, timeout, unknown user)

Parallelization

  • Uses joblib's Parallel() with multiprocessing backend
  • Single device processing: sequential (no parallelization)
  • Multiple devices: parallel with configurable job count (config.parallel_jobs)
  • Each process handles one device's complete SNMP walk and parsing
  • Frozen dataclasses are picklable, so AppConfig safely crosses process boundaries

Data Flow Example

  1. User runs: python -m inventory_monitor.main --env-file .env.staging --device "router-1" --snmp-version 2
  2. _build_config() loads config from .env.staging, overlays --snmp-version 2 via dataclasses.replace()
  3. Netbox(config=config.netbox) and InventoryMonitor(config=config) are constructed
  4. collect_device_inventory() fetches device from NetBox by name
  5. monitor.process_device() is called:
    • Extracts IP from device.primary_ip4
    • get_snmp_handler(platform, config.snmp) returns SnmpCisco(snmp_config=...)
    • snmp_session() reads self.snmp_config for credentials/timeout/retries
    • Establishes SNMP connection (only v2 since versions=[2])
    • Walks Entity MIB OIDs, collects raw items
    • Converts to human-readable format using named field constants
  6. monitor.prepare_device_sn_inventory() groups by serial number, applies priority, filters config.model_name_excludes
  7. For each serial number, netbox.create_or_update_probe() creates or updates probe
  8. Results logged to logs/all.log and logs/errors.log
  9. If --debug-dump was passed, raw SNMP data saved as JSON file

Common Modification Points

Adding a new device platform:

  1. Create new snmp/newplatform.py with SnmpNewPlatform class extending ISnmp
  2. Implement three abstract methods with platform-specific OIDs and parsing
  3. Add OID constants to snmp/constants.py (e.g. NEW_PLATFORM_FIELD = {"SERIAL": "5", ...})
  4. Add to PLATFORM_HANDLERS dict in snmp/__init__.py: "platform_name": SnmpNewPlatform
  5. Re-export the class in snmp/__init__.py
  6. Test with device having matching platform in NetBox

Changing inventory priority:

  • Modify inventory_priority_classes list in InventoryMonitor.prepare_device_sn_inventory()

Adjusting NetBox filters:

  • Default filters (active status, has_primary_ip) are built inline in collect_device_inventory() in main.py
  • Use NETBOX_TAGS and NETBOX_TAGS__N env vars (or config.netbox.tags / config.netbox.tags_exclude) for tag-based filtering

Adding a new CLI option:

  1. Add @click.option() decorator to main() in main.py
  2. Add the parameter to the CliOverrides dataclass
  3. Add merge logic in _build_config() using dataclasses.replace()
  4. If it's a new config field, add it to the appropriate dataclass in models.py

Using multiple environment files:

  • Create environment-specific files: .env.production, .env.staging, .env.dev
  • Use --env-file to specify which config to load
  • Combine with CLI overrides for additional flexibility
  • Example: python -m inventory_monitor.main --env-file .env.staging --device test-router

Custom logging:

  • Adjust setup_logger() in logger.py for file rotation size, retention, format
  • Log level controlled via LOG_LEVEL env var or config.log_level

Dependencies

Core dependencies (from setup.py):

  • easysnmp: SNMP protocol library (requires net-snmp system package)
  • pynetbox: NetBox API client
  • python-dotenv: Environment variable loading
  • joblib: Parallel job execution
  • loguru: Structured logging
  • click: CLI framework