3 Commits

Author SHA1 Message Date
9e28004d6c Strip SRV prefix from inventory host keys
All checks were successful
Build and Release / Build Windows Exe (push) Successful in 11s
2026-02-06 15:56:58 -05:00
8b3584fa9e Use server reference as inventory key and add ansible_host var
All checks were successful
Build and Release / Build Windows Exe (push) Successful in 10s
2026-02-06 15:51:30 -05:00
a202e267f7 Capture Production IP from Servers tab and use for flow matching
All checks were successful
Build and Release / Build Windows Exe (push) Successful in 11s
2026-02-06 15:43:06 -05:00
5 changed files with 73 additions and 11 deletions

31
test_inventory_keys.py Normal file
View File

@@ -0,0 +1,31 @@
import unittest
from wif2ansible.models import Server, Flow
from wif2ansible.inventory import generate_inventory
class TestInventoryKeys(unittest.TestCase):
def test_inventory_keys_are_hostnames(self):
# Create a server with Ref, Hostname, IP
s1 = Server(reference="SERVER_REF_01", hostname="server01", ip_address="192.168.1.10", platform="windows")
# Create a flow matching this server
f1 = Flow(flow_id="1", source_ip="192.168.1.10", destination_ip="10.0.0.1", ports=[80])
servers = {"SERVER_REF_01": s1}
flows = [f1]
inventory = generate_inventory(servers, flows)
# Verify stricture
hosts = inventory['all']['hosts']
# Key should be REFERENCE "SERVER_REF_01" (or hostname/ip fallback)
self.assertIn("SERVER_REF_01", hosts)
self.assertNotIn("192.168.1.10", hosts)
# Check variables
host_vars = hosts["SERVER_REF_01"]
self.assertEqual(host_vars['ansible_host'], "192.168.1.10")
self.assertEqual(host_vars['ansible_connection'], "winrm")
if __name__ == '__main__':
unittest.main()

View File

@@ -2,7 +2,7 @@ import openpyxl
from openpyxl.worksheet.worksheet import Worksheet from openpyxl.worksheet.worksheet import Worksheet
from typing import List, Dict, Tuple, Optional from typing import List, Dict, Tuple, Optional
from .models import Server, Flow from .models import Server, Flow
from .parsers import parse_ports, parse_ip, clean_header from .parsers import parse_ports, parse_ip, clean_header, clean_reference
from openpyxl.utils import get_column_letter from openpyxl.utils import get_column_letter
@@ -77,9 +77,8 @@ def read_servers(filename: str) -> Dict[str, Server]:
print("Warning: No 'Servers' sheet found.") print("Warning: No 'Servers' sheet found.")
return {} return {}
# keywords: reference, platform, ip address, management ip? # keywords: reference, platform, ip address, management ip, production ip
# Ruby script looked for: reference, type, alias, platform, middleware header_keywords = ['reference', 'platform', 'ip address', 'production ip']
header_keywords = ['reference', 'platform', 'ip address']
header_row_idx, col_map = find_header_row(target_sheet, header_keywords) header_row_idx, col_map = find_header_row(target_sheet, header_keywords)
@@ -98,7 +97,8 @@ def read_servers(filename: str) -> Dict[str, Server]:
# Extract data # Extract data
ref_idx = col_map.get('reference') ref_idx = col_map.get('reference')
plat_idx = col_map.get('platform') plat_idx = col_map.get('platform')
ip_idx = col_map.get('ip address') # Generic IP ip_idx = col_map.get('ip address') # Generic/Management IP
prod_ip_idx = col_map.get('production ip') # Specific Production IP
# Helper to get value # Helper to get value
def get_val(idx): def get_val(idx):
@@ -111,19 +111,29 @@ def read_servers(filename: str) -> Dict[str, Server]:
continue continue
plat = get_val(plat_idx) or 'unknown' plat = get_val(plat_idx) or 'unknown'
ip_raw = get_val(ip_idx)
# Parse Management IP
ip_raw = get_val(ip_idx)
ip_addr = None ip_addr = None
if ip_raw: if ip_raw:
ips = parse_ip(ip_raw) ips = parse_ip(ip_raw)
if ips: if ips:
ip_addr = ips[0] # Take first valid IP ip_addr = ips[0]
# Parse Production IP
prod_ip_raw = get_val(prod_ip_idx)
prod_ip_addr = None
if prod_ip_raw:
ips = parse_ip(prod_ip_raw)
if ips:
prod_ip_addr = ips[0]
s = Server( s = Server(
reference=ref, reference=ref,
hostname=ref, # Default hostname to reference hostname=clean_reference(ref),
platform=plat, platform=plat,
ip_address=ip_addr ip_address=ip_addr,
production_ip=prod_ip_addr
) )
servers[ref] = s servers[ref] = s

View File

@@ -20,6 +20,8 @@ def generate_inventory(servers: Dict[str, Server], flows: List[Flow]) -> Dict[st
for s in servers.values(): for s in servers.values():
if s.ip_address: if s.ip_address:
ip_to_server[s.ip_address] = s ip_to_server[s.ip_address] = s
if s.production_ip:
ip_to_server[s.production_ip] = s
# Also index by reference/hostname for DNS matches # Also index by reference/hostname for DNS matches
if s.reference: if s.reference:
ip_to_server[s.reference.lower()] = s ip_to_server[s.reference.lower()] = s
@@ -59,8 +61,8 @@ def generate_inventory(servers: Dict[str, Server], flows: List[Flow]) -> Dict[st
match_count += 1 match_count += 1
# Prepare host entry if new # Prepare host entry if new
# We use the IP as the key in inventory 'hosts' # We use the Reference/Hostname as the key in inventory 'hosts'
host_key = server.ip_address host_key = server.reference or server.hostname or server.ip_address
if host_key not in inventory_hosts: if host_key not in inventory_hosts:
host_vars = server.get_ansible_vars() host_vars = server.get_ansible_vars()

View File

@@ -6,6 +6,7 @@ class Server:
reference: str reference: str
hostname: str # This might be same as reference hostname: str # This might be same as reference
ip_address: Optional[str] = None ip_address: Optional[str] = None
production_ip: Optional[str] = None
platform: str = 'unknown' # e.g. 'Windows', 'Linux' platform: str = 'unknown' # e.g. 'Windows', 'Linux'
def get_ansible_vars(self) -> Dict[str, Any]: def get_ansible_vars(self) -> Dict[str, Any]:
@@ -22,6 +23,9 @@ class Server:
# Default ssh is usually fine, but being explicit doesn't hurt # Default ssh is usually fine, but being explicit doesn't hurt
pass pass
if self.ip_address:
vars['ansible_host'] = self.ip_address
return vars return vars
@dataclass @dataclass

View File

@@ -57,6 +57,21 @@ def parse_ports(port_str: str) -> List[int]:
return sorted(list(ports)) return sorted(list(ports))
def clean_reference(ref: str) -> str:
"""
Cleans a server reference string.
Specifically removes 'SRV###' type prefixes if present.
Example: 'SRV123 MyServer' -> 'MyServer'
"""
if not ref:
return ""
s = str(ref)
# Remove SRV followed by digits and whitespace
s = re.sub(r'SRV\d+\s*', '', s, flags=re.IGNORECASE)
# Remove leading/trailing whitespace
return s.strip()
def parse_ip(ip_str: str) -> List[str]: def parse_ip(ip_str: str) -> List[str]:
"""Finds all IPv4 addresses in a string.""" """Finds all IPv4 addresses in a string."""
if not ip_str: if not ip_str: