Files
wif2ansible/wif2ansible/inventory.py
Kris Forbes dcddd88cbc
All checks were successful
Build and Release / Build Windows Exe (push) Successful in 10s
Implement DNS caching and verbose logging
2026-02-06 16:33:13 -05:00

146 lines
5.4 KiB
Python

from typing import List, Dict, Any
from .models import Server, Flow
from .models import Server, Flow
from .network import to_mgt_ip, is_valid_hostname, get_hostname
from .parsers import clean_reference
def generate_inventory(servers: Dict[str, Server], flows: List[Flow]) -> Dict[str, Any]:
"""
Generates the Ansible inventory dictionary.
servers: Dict[Reference, Server]
flows: List[Flow]
"""
# Build Lookup Map: IP -> Server
# Note: A server might have multiple IPs (e.g. Mgt, Public, Private).
# The 'Server' object mainly captures the Management IP or the one listed in the "IP Address" column.
# If the WIF has "Source Public IP" and that differs from "IP Address" in Servers tab,
# we might miss it if we only index the primary IP.
# However, strict filtering means we trust the 'Servers' tab.
ip_to_server = {}
for s in servers.values():
# Index all Management IPs
for ip in s.ip_addresses:
ip_to_server[ip] = s
# Index all Production IPs
for ip in s.production_ips:
ip_to_server[ip] = s
# Also index by reference/hostname for DNS matches
if s.reference:
ip_to_server[s.reference.lower()] = s
if s.hostname:
ip_to_server[s.hostname.lower()] = s
inventory_hosts = {}
# Process flows
match_count = 0
drop_count = 0
total_flows = len(flows)
print(f"Starting inventory generation for {total_flows} flows...")
for idx, flow in enumerate(flows, 1):
if idx % 10 == 0:
print(f"Processing flow {idx}/{total_flows}...")
# Find source server
server = ip_to_server.get(flow.source_ip)
if not server:
# Try DNS resolution (Public IP -> Management FQDN)
print(f"Flow {idx}: Source {flow.source_ip} not found in map. Attempting DNS resolution...")
mgt_dns = to_mgt_ip(flow.source_ip)
if mgt_dns:
# mgt_dns might be "server.ds.gc.ca".
# Our keys might be "server" or "server.ds.gc.ca" or IPs
# Try exact match
server = ip_to_server.get(mgt_dns.lower())
# If not found, try shortname?
if not server:
short = mgt_dns.split('.')[0]
server = ip_to_server.get(short.lower())
if not server:
drop_count += 1
if drop_count <= 10: # Increased debug spam limit
print(f"Dropping flow {flow.flow_id} ({idx}/{total_flows}): Source {flow.source_ip} (Mgt: {mgt_dns}) resolved but not found in Servers tab.")
continue
else:
print(f"Flow {idx}: Resolved {flow.source_ip} -> {server.hostname or server.reference}")
match_count += 1
# Prepare host entry if new
# Candidate Resolution Logic
# User Requirement: "gather all potential names ... check to see what actually resolves"
candidates = []
# 1. Server Name Column (Highest priority from Excel)
if server.hostname:
candidates.append(server.hostname)
# 2. Cleaned Reference (Fallback from Excel)
if server.reference:
candidates.append(clean_reference(server.reference))
# 3. Reverse DNS of Primary IP?
# If the Excel names are garbage, maybe the IP resolves to the "Real" DNS name.
if server.primary_ip:
# Try simple reverse lookup
rev_name = get_hostname(server.primary_ip)
if rev_name:
candidates.append(rev_name)
# Select the first candidate that resolves
final_host_key = None
for cand in candidates:
if not cand: continue
if is_valid_hostname(cand):
final_host_key = cand
break
# Fallback: strict fallback to IP if nothing resolves?
# Or best effort (first candidate)?
# User said: "You are getting it incorrect every time" -> likely implying the garbage name was used.
# But if *nothing* resolves, we must output something. The IP is safe connectivity-wise, but user wants Names.
# Let's fallback to the IP if NO name works, to ensure ansible works.
if not final_host_key:
if candidates:
# Warn?
print(f"Warning: No resolvable name found for {server.primary_ip} (Candidates: {candidates}). Using IP.")
final_host_key = server.primary_ip
host_key = final_host_key
if host_key not in inventory_hosts:
host_vars = server.get_ansible_vars()
host_vars['flows'] = []
inventory_hosts[host_key] = host_vars
# Add flow
flow_entry = {
'flow_id': flow.flow_id,
'dest': flow.destination_ip,
'ports': flow.ports,
'protocol': flow.protocol
}
# Dedup check?
# Ideally we shouldn't have exact duplicates, but appending is safe.
inventory_hosts[host_key]['flows'].append(flow_entry)
print(f"Inventory Generation Report: Matches={match_count}, Dropped={drop_count}")
return {
'all': {
'hosts': inventory_hosts
}
}