Deep Dive: CDP Network Audit Tool¶
"Cisco Python Automation, Explained Line-by-Line."¶
A threaded network discovery utility that starts from one or more seed Cisco devices and crawls the topology via Cisco Discovery Protocol (CDP). It connects (optionally through an SSH jump/bastion host), collects show cdp neighbors detail and show version, parses outputs with TextFSM, enriches with DNS resolution, and writes a structured Excel report from a pre-formatted template. Designed for reliability, safe concurrency, and repeatable reporting in enterprise environments.
β¨ Highlights¶
- Parallel discovery with a worker pool (configurable via
config.pyor environment variables) - Two-tier authentication: primary user first, then customisable fallback user if primary fails
- Jump server / bastion support (Paramiko channel + Netmiko sock)
- DNS enrichment for discovered hostnames
- Excel report written from a pre-formatted template with multiple sheets
- Hybrid logging: optional
logging.conf; sensible defaults otherwise - Centralized configuration via comprehensive
config.pywith 200+ lines of documented settings - Fully customisable including fallback username, credential targets, jump host, Excel formatting, and more
π― The Nautomation Prime Philosophy in Action¶
Before diving into the code, understand how every design decision reflects our three core principles:
Principle 1: Line-by-Line Transparency¶
Every function in this tool is extensively documented. We don't just explain what the code doesβwe explain why it's structured this way and what engineering tradeoffs we made.
Principle 2: Hardened for Production¶
You'll notice patterns like thread locks, exception handling, retry logic, and graceful cleanup. These aren't "nice to have"βthey're essential for running automation on critical infrastructure without surprises.
Principle 3: Vendor-Neutral¶
This tool is built on industry-standard libraries: Netmiko (SSH connection handling), Paramiko (SSH tunnelling), Pandas & OpenPyXL (Excel reporting), and TextFSM (parsing). Your skills remain portable.
π§± Repository Layout (Expected)¶
Note: Paths are case-sensitive on Linux/macOS; keep names exactly as shown. Key Change: Configuration is now in human-readable YAML format at the project root, loaded via
config_loader.py.
π¦ Requirements¶
- Python: 3.8+
- Python packages:
pandas,openpyxl,textfsm,paramiko,netmiko - (Windows only, optional)
pywin32for Windows Credential Manager integration
Install in one go:
Tested Devices¶
This tool has been tested and verified on the following Cisco IOS and IOS-XE platforms:
- Catalyst 9200 Series
- Catalyst 3650 Series
- Catalyst 3650C
- Catalyst 3650CG
- Catalyst 3650CX
- Catalyst 2960X Series
- Catalyst 2960 Series
Note: The tool should work with any Cisco IOS/IOS-XE device that supports CDP and the required show commands. The devices listed above have been explicitly tested and validated.
Required Support Files¶
- TextFSM templates:
ProgramFiles/textfsm/cisco_ios_show_cdp_neighbors_detail.textfsmProgramFiles/textfsm/cisco_ios_show_version.textfsm- Excel template:
ProgramFiles/config_files/1 - CDP Network Audit _ Template.xlsx
The script validates presence of these files at startup and exits if any are missing.
βοΈ Configuration Options¶
The tool uses a comprehensive YAML-based configuration system with two configuration methods:
Method 1: config.yaml (Primary) β YAML Configuration¶
All configurable settings are centralized in a YAML configuration file located at the project root.
The config.yaml file provides a human-readable, version-control-friendly format for all settings. The configuration is loaded via ProgramFiles/config_files/config_loader.py which provides a Config class with property accessors for type-safe configuration access.
Key settings include:
Network Connection Settings (YAML):
Performance Settings (YAML):
Credential Settings (YAML):
Why YAML Configuration?
- Human-Readable: Easy to read and edit without Python knowledge
- Version Control Friendly: Plain text format works seamlessly with Git
- Hierarchical Structure: Natural grouping of related settings
- Comment Support: Inline documentation stays with configuration
- Type Safety: Config loader validates types and provides sensible defaults
- No Code Execution: Unlike Python config files, YAML is data-only (safer)
File Paths (YAML):
Excel Report Settings (YAML):
Note: The config.yaml file contains comprehensive settings covering network, credentials, paths, Excel formatting, DNS, logging, and more. See the file directly for complete details and usage examples.
Method 2: Environment Variables (Override)¶
Environment variables can override specific config.yaml settings at runtime:
| Variable | Description | config.yaml Default |
|---|---|---|
CDP_LIMIT |
Max concurrent worker threads | 10 |
CDP_TIMEOUT |
SSH/auth/read timeouts (seconds) | 10 |
CDP_JUMP_SERVER |
Jump host (IP/hostname). Empty = direct | "" |
CDP_PRIMARY_CRED_TARGET |
CredMan target for primary creds | MyApp/ADM |
CDP_FALLBACK_CRED_TARGET |
CredMan target for fallback creds | MyApp/Answer |
CDP_FALLBACK_USERNAME |
Fallback username | answer |
LOGGING_CONFIG |
Path to INI logging config | ProgramFiles/Config_Files/logging.conf |
Example (Windows PowerShell):
When to Use Environment Variables:
- Temporary overrides for testing
- Different settings per environment (dev/staging/prod)
- CI/CD pipelines with dynamic configuration
- Running multiple instances with different settings
Best Practice: Use config.yaml for persistent organizational defaults, and environment variables for runtime-specific overrides.
ποΈ Technical Architecture¶
The tool operates as a modular Python application with four primary components:
| Component | Responsibility | Why It Matters |
|---|---|---|
| CredentialManager | Secure credential collection and OS integration | Passwords stay out of code and config files |
| NetworkDiscoverer | Multi-threaded topology crawling via CDP | Discovers 50+ devices in seconds, not minutes |
| ExcelReporter | Professional, templated report generation | Maintains business branding and formatting |
| Logging & Validation | Pre-flight checks and operational visibility | Catches problems early; provides audit trail |
π Credentials Model¶
This tool supports a primary credential and a fully customizable fallback credential:
- Primary credentials (used for the jump and the device): read from Windows Credential Manager if present (default target
MyApp/ADM), else prompted. You can optionally save what you type back to Credential Manager. - Fallback credentials (device hop only, jump still uses primary): username is fully customizable via config.yaml (default:
answer). Password is read from Credential Manager (default targetMyApp/Answer) or prompted; you may choose to save it.
Note: On non-Windows platforms, prompts are used (no Credential Manager).
Customization: Change the fallback username in
config.yamlby settingcdp_fallback_usernameunder thecredentialssection to match your environment (e.g.,localadmin,backup,netops,svc_network).
Why Credential Management Matters¶
The Problem: Network automation requires credentials. Storing them in plaintext files or hardcoding them in scripts is a security nightmare. Even prompting users every time is error-prone and doesn't scale to 10+ discovery jobs daily.
The Solution: Leverage native OS credential stores. Windows has Credential Manager, macOS has Keychain, Linux has pass. These are designed for exactly this use case and integrate with enterprise SSO/password managers.
User Experience: When you run CDP Network Audit for the first time, the script checks Windows Credential Manager for stored credentials. If none are found, it prompts you to enter your username and password. Once you provide them, the script saves them to Windows Credential Manager and uses them for the discovery process. On subsequent runs, the script retrieves the stored credentials automatically without prompting you again.
CredentialManager.__init__()¶
Line-by-Line:
- Import the Config class from
config_loader.pywhich parsesconfig.yaml - Instantiate Config to load YAML settings with validation and type safety
- Environment variables override YAML config if set (for runtime flexibility)
- Three configurable values: primary target, fallback target, and fallback username
- The config loader validates YAML syntax and provides defaults for missing values
Why This Matters:
- config.yaml: Human-readable, persistent, version-controlled settings that match your organization's standards
- Environment variables: Runtime overrides for different environments (dev/prod) or testing
- Config loader: Provides type-safe property accessors and validation
- Fallback username: No longer hardcodedβcustomize in config.yaml to match your local accounts (e.g.,
localadmin,netops,backup) - Separation of concerns: Configuration data (YAML) is separate from configuration logic (Python class)
- Credentials themselves are still stored securely in Credential Manager (Windows) or prompted on other platforms
_read_win_cred(target_name: str)¶
Reads encrypted credentials from Windows Credential Manager. Returns (username, password) tuple or (None, None) if not found.
Key Points:
- Only imports
win32credif available (Windows only) - Handles both bytes and string returns for compatibility
- Decodes password from UTF-16LE (Windows internal format)
- Gracefully fails and returns None instead of crashing
Why This Approach:
- No plaintext storage: Credentials are encrypted by Windows
- Cross-platform: Non-Windows systems skip this and use prompts
- Version-agnostic: Works with multiple pywin32 versions
_write_win_cred(target: str, username: str, password: str) -> bool¶
Writes credentials to Windows Credential Manager for future reuse. Users can optionally save credentials after first prompt.
Key Points:
- Password is encoded to UTF-16LE before storage (Windows requirement)
CRED_PERSIST_LOCAL_MACHINEmeans credentials persist across sessions- Failures are logged at DEBUG level (not alarming)
- Returns
Trueon success,Falseon failure
Why This Matters:
- Users can avoid re-prompting on subsequent runs
- Credentials are encrypted and protected by Windows
- Optional save means users control persistence
get_secret_with_fallback(...) -> Tuple[str, str]¶
The credential retrieval orchestrator with multi-step fallback:
- Try Credential Manager first (if Windows)
- Fall back to interactive prompt
- Optionally save to Credential Manager
Two-Credential Model:
- Primary: Your main automation account (flexible username, likely AD-backed)
- Fallback: A secondary user on each device (username customisable in
config.py, typically a local account)
Why This Design:
- Zero installation friction - first run prompts, subsequent runs use saved credentials
- Two credentials maximise success: primary fails β retry with fallback
- Jump host always uses primary (tighter control)
- Device can fall back to secondary user (local account)
- Fully customisable to match your organisation's account naming standards
π Jump Server Behaviour¶
- Set
jump_hostin thenetworksection ofconfig.yamlto specify a default jump host. - Alternatively, use the
CDP_JUMP_SERVERenvironment variable to override at runtime. - If empty, you will be prompted during runtime; leaving it blank uses direct device connections.
- The jump is created with Paramiko and a
direct-tcpipchannel; Netmiko is then bound to that channel (no local listener required).
Note: Host key policy defaults to a warning (accepts unknown keys but logs a warning). For production environments, prefer strict host key checking via
known_hostsmanagement.Tip: Configure your jump server in
config.yaml(jump_host: "192.0.2.10") for permanent use, or leave it empty to be prompted each time for flexibility.
π Quick Start: Using the Launcher (Recommended)¶
The repository now includes a professional Windows batch launcher (run.bat) that provides the easiest way to run the tool with default settings.
Why Use the Launcher?¶
- Zero configuration required - Just double-click or run from command line
- Automatic validation - Checks for Python environment and required files before execution
- Helpful diagnostics - Clear error messages if something is missing
- Professional interface - Clean output with status indicators and progress messages
- Safe execution - Validates environment before running the script
Using run.bat¶
Option 1: Double-click¶
Simply double-click run.bat in Windows Explorer to launch the tool with default behavior.
Option 2: Command Line (Default Behavior)¶
This runs the CDP Network Audit with all default settings from config.yaml.
What the Launcher Does¶
- Validates the environment:
- Checks that the
portable_envvirtual environment exists - Verifies Python executable is present
- Confirms
main.pyexists - Validates required TextFSM templates are present
-
Validates Excel template exists
-
Provides clear feedback:
- Shows [OK] for successful checks
- Shows [WARNING] for missing optional files
- Shows [ERROR] for critical missing components
-
Displays helpful troubleshooting tips on failure
-
Runs the tool:
- Activates the virtual environment
- Executes the main script
- Captures and displays the exit code
- Provides common troubleshooting tips if errors occur
Example Output¶
π Advanced: Command Line with Arguments¶
For advanced users who need to customize behavior beyond the defaults, you can still run the tool directly with Python and command-line arguments.
When to Use Command Line Arguments¶
Use python main.py with arguments when you need to:
- Override default settings from
config.yaml - Run with different parameters for testing
- Integrate with automation scripts
- Pass runtime-specific configuration
Method 1: Using the Launcher with Arguments¶
You can pass arguments to run.bat and they will be forwarded to the Python script:
Method 2: Direct Python Execution¶
π How to Run (Interactive Flow)¶
- Ensure templates and Excel file exist under
ProgramFiles/...(see above). - (Optional) Customize
config.yamlwith your organization's defaults. - (Optional) Set environment variables as needed for runtime overrides.
- Run:
- Follow prompts:
- Site name (used in the output filename)
- Seed devices (comma-separated IPv4 / resolvable hostnames)
- Primary credentials (reads from CredMan if present; else prompts; optional save)
- Fallback password (username from
config.yaml; reads from CredMan if present; else prompts; optional save) - Jump server (from
config.yaml, env var, or prompt; blank = direct)
The tool validates/normalises seeds to IP addresses, de-duplicates them, then starts the threaded discovery.
π§ͺ What Gets Collected¶
For each visited device the tool attempts to collect:
show version(hostname, serials, uptime) β for local context.show cdp neighbors detailβ parsed into structured rows.- DNS resolution for all discovered hostnames (best-effort), in parallel.
Discovery Heuristics¶
- Only Switch-capable CDP entries (and not hosts) with a management IP are queued as crawl candidates.
- Deduplication is performed by hostname and IP to reduce churn.
- Each target is retried up to 3 times for transient connectivity issues.
prompt_for_inputs()¶
Orchestrates all interactive input collection in one flow:
- Site name - Used in Excel filename (max 50 chars)
- Seed devices - Comma-separated IPs or hostnames
- Primary credentials - Main automation account
- Fallback credentials - Fallback device account (username from
config.py, default:answer)
π NetworkDiscoverer: The Threaded Discovery Engine¶
Why Parallel Discovery is Essential¶
The Problem: Discovering 50+ switches serially takes 10+ minutes. Each SSH connection is a round-trip: connect, execute, disconnect.
The Solution: Thread pool with 10 concurrent workers = 5x faster. 10 simultaneous connections instead of waiting for each one.
Thread-Safe Data Accumulators¶
Why Two Locks?
- If we used one lock for everything, threads would block each other constantly
- Granular locks allow more independent work
visited_lockfor quick "is this already being processed?" checksdata_lockfor appending results
parse_outputs_and_enqueue_neighbors(...)¶
This is the intelligence of the discovery engine. It decides which devices to audit next.
Three-Step Process:
Step 1: Parse Device Context¶
- Extract hostname, serial, uptime from
show version - Fall back to IP if parsing fails
Step 2: Parse CDP Neighbors¶
- Extract each neighbor's details (ports, capabilities, management IP)
- Store in thread-safe list
Step 3: Apply Queueing Heuristic¶
Only enqueue if ALL three conditions are true:
Why "Switch" in caps?
- CDP capability strings like "Switch Router" identify infrastructure
- We only want to audit infrastructure nodes, not endpoints
Why "Host" not in caps?
- IP phones, printers, cameras also show up in CDP
- Their capability includes "Host" but we can't/shouldn't manage them
Why mgmt_ip?
- If a device doesn't advertise a management IP, we have no way to SSH to it
- Queueing it would just cause failures
Example:
_paramiko_jump_client(...) -> paramiko.SSHClient¶
Creates a secure SSH connection to a jump/bastion host.
Key Design Choices:
WarningPolicy()- Log warnings for unknown hosts (safer than AutoAddPolicy)- Explicit password auth only - No SSH keys or agent (easier to audit)
- Consistent timeouts - All operations respect
CDP_TIMEOUTsetting - Re-raise auth failures - Let caller handle credential issues
Why WarningPolicy?
- Accepts unknown hosts but logs warnings
- Catches potential man-in-the-middle attacks without crashing
- Production-ready security posture
_netmiko_via_jump(...) -> ConnectHandler¶
The core connection function. Handles both direct and jump-host connections.
Credential Logic:
Why This Two-Credential Model:
- Jump host always uses primary (tightest control)
- Device can use fallback if primary fails (username customisable in
config.py) - Resilience: if your primary account is locked, fallback account can still work
- Flexibility: adapt to your organisation's local account naming conventions
Direct Connection: Simply pass device IP to Netmiko.
Jump-Mediated Connection:
- Open Paramiko SSH to jump host
- Create
direct-tcpipchannel (SSH tunnel) through jump to target - Wrap channel as socket
- Pass socket to Netmiko for SSH auth
Why direct-tcpip?
- No need to open a listener on the jump host
- No port forwarding configuration required
- All traffic is inside the already-authenticated SSH session
- Secure and clean
run_device_commands(...) -> Tuple[str, str]¶
Executes CDP and version commands on target device with fallback credentials.
Strategy:
- Try with primary credentials
- On auth failure, catch and retry with fallback (customisable fallback user on device)
- Don't retry auth failures (credentials won't change between attempts)
- Do retry transient errors (timeouts, SSH glitches) up to 3 times
- Always disconnect in finally block (prevent socket leaks)
Why This Approach:
- Maximizes success rate with two-credential strategy
- Transient timeouts are retried (network glitches happen)
- Auth failures fail-fast (no point retrying)
- Finally block ensures resource cleanup
discover_worker(...) -> None¶
The worker thread function. Multiple instances run concurrently.
Loop:
- Get next host from queue (timeout=1.0 prevents hangs)
- Recognize sentinel (None = shutdown signal)
- Check if already visited (prevent duplicate work)
- Execute discovery with up to 3 retries
- Parse outputs and enqueue new neighbors
- Always call
task_done()or queue.join() will hang
Why Sentinel Pattern?
- None signals worker to exit gracefully
- Main thread sends one sentinel per worker
- Coordinated shutdown without races
Why Check If Already Visited?
- Concurrent workers might both process same IP
- Prevent duplicate discovery work
- Track with hostname and IP
Why task_done() Is Critical:
Without task_done(), queue.join() waits forever on main thread. This is a common source of hangs in multi-threaded code!
resolve_dns_parallel() -> None¶
After discovery, resolve all discovered hostnames to IPs in parallel.
Design:
- ThreadPoolExecutor with 4-32 workers (based on CDP_LIMIT)
- Submit all resolutions concurrently
- Collect results as they complete (don't wait for slowest)
- Best-effort - failures are logged but don't block
Why Separate from Discovery?
- DNS lookups are independent
- Can run in smaller thread pool (4-32 vs. 10)
- Doesn't block discovery if DNS is slow
π ExcelReporter: Professional Reporting¶
Why Professional Reporting Matters¶
The Problem: Raw CSV or unformatted Excel is not useful for business. Reports need context, formatting, branding.
The Solution: Use a pre-formatted Excel template. Write data into it while preserving all formatting, charts, filters, and branding.
Template-Driven Approach¶
Step 1: Copy Template¶
Preserve metadata (timestamps, permissions).
Step 2: Stamp Metadata¶
Fill cells B4-B8 with audit metadata.
Step 3: Append Data¶
Use if_sheet_exists="overlay" mode to append without destroying template.
Data starts at row 12 (after headers and metadata).
Why This Approach:
- Template-driven: Business controls formatting without touching code
- Non-destructive: Data is appended, template is preserved
- Professional: Charts, filters, styling all maintained
- Automated: No manual Excel editing required
Output File Details¶
An output file named <site_name>_CDP_Network_Audit.xlsx is created by copying the template.
Sheets¶
- Audit β Main CDP dataset. Also stamped with metadata:
B4: Site nameB5: DateB6: TimeB7: Primary seedB8: Secondary seed (or "Secondary Seed device not given")- DNS Resolved β Two columns:
Hostname,IP Address - Authentication Errors β One column:
Authentication Errors(IP list) - Connection Errors β Two columns:
IP Address,Error
Columns in Audit (Data Rows)¶
LOCAL_HOST, LOCAL_IP, LOCAL_PORT, LOCAL_SERIAL, LOCAL_UPTIME, DESTINATION_HOST, REMOTE_PORT, MANAGEMENT_IP, PLATFORM.
Note: The template governs formatting/filters/charts (if any). The writer appends data starting at the appropriate row offsets to preserve the layout.
π Key Design Patterns¶
Pattern 1: Thread-Safe Data Accumulation¶
Only one thread updates shared data at a time.
Pattern 2: Graceful Worker Shutdown¶
Pattern 3: Retry with Fallback Credentials¶
Pattern 4: Resource Cleanup in Finally¶
Pattern 5: Template-Driven Reporting¶
Copy β stamp metadata β append data using overlay mode.
π§° Logging¶
- If a config file is present, logging is configured via
logging.config.fileConfig(). - Otherwise, a basic console logger is configured at INFO with timestamps.
- You can set
LOGGING_CONFIGto point to an INI file anywhere; if not set, the tool looks forProgramFiles/Config_Files/logging.conf.
π§― Errors & Retry Behaviour¶
- Authentication failures: the host is recorded under Authentication Errors.
- Connectivity/timeouts: the host is recorded under Connection Errors with the last error tag (e.g.,
NetmikoTimeoutException,SSHException,socket.timeout). - Retries: up to 3 attempts for each device before recording a connection error.
- Graceful exit: workers always
task_done()to avoid queue hangs.
π Performance¶
- Worker threads =
CDP_LIMIT(default 10). - DNS resolution runs in a small parallel pool after discovery.
- Use a conservative limit on older/CPU-bound platforms; increase on fast links.
π Security Considerations¶
- Prefer Credential Manager (Windows) or other secret stores instead of plaintext.
- Ensure jump host is hardened; consider strict host key verification.
- Output workbooks can contain sensitive topology data β share on a need-to-know basis.
β Exit Codes¶
- 0 β Success
- 1 β Required TextFSM or Excel template missing / invalid
- 130 β Interrupted by user (Ctrl+C)
β Example Session¶
Note: The fallback username shown in the prompt reflects your
config.yamlsetting. Default is 'answer', but you can customise it to 'localadmin', 'backup', etc. by editing thecredentialssection ofconfig.yaml.
If you had customized your config.yaml:
The prompt would show:
π οΈ Customisation Points¶
- User settings: Edit
config.yamlto customise worker threads, timeouts, jump server, credential targets, and fallback username. - Template paths: Adjust in
config.yamlunder thepathssection. - Queueing heuristics (which neighbors to crawl): Modify
parse_outputs_and_enqueue_neighbors()inmain.py. - Retry counts / timeouts: Configure in
config.yamlunderperformanceor override via environment variables. - Logging: Provide a
logging.confthat matches your standards (path configurable inconfig.yaml). - Fallback account: Set
cdp_fallback_usernameinconfig.yaml(undercredentials) to match your local admin account naming. - Excel formatting: Customize cell locations and sheet names in
config.yamlunderexcel.
Example config.yaml Customization:
π Learning Outcomes¶
After studying this code, you should understand:
β
Concurrent programming β How thread pools and locks prevent race conditions
β
SSH tunnelling β How direct-tcpip channels work and why they're safer
β
Credential management β OS-level credential stores vs. plaintext files
β
TextFSM parsing β How to extract structured data from CLI output
β
Error handling β Retry strategies and graceful degradation
β
Excel automation β Template-driven reporting with data overlay
β
Network discovery β CDP heuristics and neighbor crawling logic
π Distribution & Execution¶
Consistent with the Nautomation Prime delivery model, this tool is available in multiple formats:
-
Zero-Install Portable Bundle: A self-contained package including the Python interpreter and all libraries (Netmiko, Pandas, TextFSM) for use on restricted Windows jump boxes.
-
Scheduled Docker Appliance: A pre-built container designed for autonomous execution and periodic auditing.
π Repository & Downloads¶
Ready to audit your own network? Access the hardened source code and pre-configured templates below.
- View Full Repository: Access the code, TextFSM templates, and Excel master.
- Download Latest Release: Get a clean ZIP of the production-ready files.
π Performance Tuning¶
| Scenario | Configuration | Rationale |
|---|---|---|
| Fast LAN, many devices | CDP_LIMIT=20, CDP_TIMEOUT=5 |
High concurrency, short timeouts work |
| Slow WAN link | CDP_LIMIT=5, CDP_TIMEOUT=30 |
Fewer threads prevent overwhelming network; higher timeout for round-trip delay |
| Mixed (some LAN, some WAN) | CDP_LIMIT=10, CDP_TIMEOUT=10 |
Balanced defaults |
| Device with high CPU | CDP_LIMIT=3-5 |
Fewer threads prevent overwhelming device |
π¬ Next Steps¶
- Clone the repository:
git clone https://github.com/Nautomation-Prime/Cisco_CDP_Network_Audit - Customise config.yaml to match your environment:
- Set
jump_hostundernetworksection - Customize
cdp_fallback_usernameundercredentialssection - Adjust
default_limitanddefault_timeoutunderperformancesection - Configure credential targets if different from defaults
- Read the README for installation and configuration details
- Set up credentials in Windows Credential Manager (or let the script prompt you on first run)
- Run your first discovery against a test device
- Review the Excel output to understand the report format
Once comfortable, customise the discovery heuristics and template for your specific topology.
Example config.yaml for Enterprise Use:
π Licence¶
GNU General Public Licence v3.0
π€ Author¶
Christopher Davies
Mission Statement: To empower engineers through Python-driven transparency and provide enterprises with hardened automation that eliminates error and accelerates growth.