mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-07-17 22:14:25 +02:00
105 lines
3.9 KiB
Python
105 lines
3.9 KiB
Python
import os
|
|
import unittest
|
|
import yaml
|
|
import glob
|
|
import ipaddress
|
|
|
|
class TestNetworksUniqueValidAndMapped(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
# locate group_vars/all/10_networks.yml
|
|
base_dir = os.path.dirname(__file__)
|
|
cls.networks_file = os.path.abspath(
|
|
os.path.join(base_dir, '..', '..', 'group_vars', 'all', '10_networks.yml')
|
|
)
|
|
if os.path.isfile(cls.networks_file):
|
|
with open(cls.networks_file, 'r', encoding='utf-8') as f:
|
|
cls.networks_data = yaml.safe_load(f)
|
|
else:
|
|
cls.networks_data = None
|
|
|
|
def test_networks_file_exists(self):
|
|
"""Fail if the networks file is missing."""
|
|
self.assertTrue(
|
|
os.path.isfile(self.networks_file),
|
|
f"{self.networks_file} does not exist."
|
|
)
|
|
|
|
def test_unique_and_non_overlapping_subnets(self):
|
|
"""Ensure that all subnets are valid, unique and do not overlap."""
|
|
if self.networks_data is None:
|
|
self.skipTest("10_networks.yml not found, skipping subnet validation.")
|
|
|
|
# extract all named subnets under defaults_networks.local
|
|
local = self.networks_data.get('defaults_networks', {}).get('local', {})
|
|
name_to_net = {}
|
|
for name, cfg in local.items():
|
|
subnet = cfg.get('subnet')
|
|
if not subnet:
|
|
continue
|
|
try:
|
|
net = ipaddress.ip_network(subnet)
|
|
except ValueError as e:
|
|
self.fail(f"Invalid subnet for network '{name}': {subnet} ({e})")
|
|
name_to_net[name] = net
|
|
|
|
# check for duplicate subnets
|
|
nets = list(name_to_net.values())
|
|
if len(nets) != len(set(nets)):
|
|
seen = {}
|
|
dupes = []
|
|
for nm, net in name_to_net.items():
|
|
if net in seen:
|
|
dupes.append(f"{seen[net]} and {nm} both use {net}")
|
|
else:
|
|
seen[net] = nm
|
|
self.fail("Duplicate subnets detected:\n" + "\n".join(dupes))
|
|
|
|
# check for overlaps
|
|
items = list(name_to_net.items())
|
|
for i in range(len(items)):
|
|
name1, net1 = items[i]
|
|
for j in range(i+1, len(items)):
|
|
name2, net2 = items[j]
|
|
if net1.overlaps(net2):
|
|
self.fail(
|
|
f"Subnet overlap between '{name1}' ({net1}) and "
|
|
f"'{name2}' ({net2})."
|
|
)
|
|
|
|
def test_network_names_mapped_to_application_id(self):
|
|
"""
|
|
Ensure each network name with a subnet under defaults_networks.local
|
|
matches an application_id in some roles/*/vars/main.yml.
|
|
"""
|
|
if self.networks_data is None:
|
|
self.skipTest("10_networks.yml not found, skipping application_id mapping check.")
|
|
|
|
# collect network names
|
|
local = self.networks_data.get('defaults_networks', {}).get('local', {})
|
|
network_names = [name for name, cfg in local.items() if 'subnet' in cfg]
|
|
|
|
# gather all application_id values from roles/*/vars/main.yml
|
|
base_dir = os.path.dirname(__file__)
|
|
roles_dir = os.path.abspath(os.path.join(base_dir, '..', '..', 'roles'))
|
|
app_ids = set()
|
|
for role_path in glob.glob(os.path.join(roles_dir, '*')):
|
|
vars_file = os.path.join(role_path, 'vars', 'main.yml')
|
|
if not os.path.isfile(vars_file):
|
|
continue
|
|
with open(vars_file, 'r', encoding='utf-8') as f:
|
|
data = yaml.safe_load(f) or {}
|
|
app_id = data.get('application_id')
|
|
if app_id:
|
|
app_ids.add(app_id)
|
|
|
|
missing = [nm for nm in network_names if nm not in app_ids]
|
|
if missing:
|
|
self.fail(
|
|
"The following networks have no matching application_id in any role:\n"
|
|
+ ", ".join(missing)
|
|
)
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|