#!/usr/bin/env python3 """ Validate autoinstall configuration against Ubuntu's schema. This script provides basic validation to check if our autoinstall config complies with the official schema structure. """ import yaml import sys from pathlib import Path def load_autoinstall_config(template_path: str) -> dict: """Load the autoinstall configuration from the template file.""" with open(template_path, "r") as f: content = f.read() # Parse the cloud-config YAML config = yaml.safe_load(content) # Extract the autoinstall section if "autoinstall" in config: return config["autoinstall"] else: raise ValueError("No autoinstall section found in cloud-config") def validate_required_fields(config: dict) -> list: """Validate required fields according to schema.""" errors = [] # Check version field (required) if "version" not in config: errors.append("Missing required field: version") elif not isinstance(config["version"], int) or config["version"] != 1: errors.append("Invalid version: must be integer 1") return errors def validate_identity_section(config: dict) -> list: """Validate identity section.""" errors = [] if "identity" in config: identity = config["identity"] required_fields = ["username", "hostname", "password"] for field in required_fields: if field not in identity: errors.append(f"Identity section missing required field: {field}") # Additional validation if "username" in identity and not isinstance(identity["username"], str): errors.append("Identity username must be a string") if "hostname" in identity and not isinstance(identity["hostname"], str): errors.append("Identity hostname must be a string") return errors def validate_network_section(config: dict) -> list: """Validate network section.""" errors = [] if "network" in config: network = config["network"] if "version" not in network: errors.append("Network section missing required field: version") elif network["version"] != 2: errors.append("Network version must be 2") return errors def validate_keyboard_section(config: dict) -> list: """Validate keyboard section.""" errors = [] if "keyboard" in config: keyboard = config["keyboard"] if "layout" not in keyboard: errors.append("Keyboard section missing required field: layout") return errors def validate_ssh_section(config: dict) -> list: """Validate SSH section.""" errors = [] if "ssh" in config: ssh = config["ssh"] if "install-server" in ssh and not isinstance(ssh["install-server"], bool): errors.append("SSH install-server must be boolean") if "authorized-keys" in ssh and not isinstance(ssh["authorized-keys"], list): errors.append("SSH authorized-keys must be an array") if "allow-pw" in ssh and not isinstance(ssh["allow-pw"], bool): errors.append("SSH allow-pw must be boolean") return errors def validate_packages_section(config: dict) -> list: """Validate packages section.""" errors = [] if "packages" in config: packages = config["packages"] if not isinstance(packages, list): errors.append("Packages must be an array") else: for i, package in enumerate(packages): if not isinstance(package, str): errors.append(f"Package at index {i} must be a string") return errors def validate_commands_sections(config: dict) -> list: """Validate early-commands and late-commands sections.""" errors = [] for section_name in ["early-commands", "late-commands"]: if section_name in config: commands = config[section_name] if not isinstance(commands, list): errors.append(f"{section_name} must be an array") else: for i, command in enumerate(commands): if not isinstance(command, (str, list)): errors.append( f"{section_name} item at index {i} must be string or array" ) elif isinstance(command, list): for j, cmd_part in enumerate(command): if not isinstance(cmd_part, str): errors.append( f"{section_name}[{i}][{j}] must be a string" ) return errors def validate_shutdown_section(config: dict) -> list: """Validate shutdown section.""" errors = [] if "shutdown" in config: shutdown = config["shutdown"] valid_values = ["reboot", "poweroff"] if shutdown not in valid_values: errors.append(f"Shutdown must be one of: {valid_values}") return errors def main(): """Main validation function.""" template_path = Path(__file__).parent / "cloud-init-template.yaml" if not template_path.exists(): print(f"Error: Template file not found at {template_path}") sys.exit(1) try: # Load the autoinstall configuration print(f"Loading autoinstall config from {template_path}") config = load_autoinstall_config(str(template_path)) # Run validation checks all_errors = [] all_errors.extend(validate_required_fields(config)) all_errors.extend(validate_identity_section(config)) all_errors.extend(validate_network_section(config)) all_errors.extend(validate_keyboard_section(config)) all_errors.extend(validate_ssh_section(config)) all_errors.extend(validate_packages_section(config)) all_errors.extend(validate_commands_sections(config)) all_errors.extend(validate_shutdown_section(config)) # Report results if all_errors: print("\n❌ Validation failed with the following errors:") for error in all_errors: print(f" - {error}") sys.exit(1) else: print("\n✅ Autoinstall configuration validation passed!") print("Configuration appears to comply with Ubuntu autoinstall schema.") # Print summary of detected sections sections = list(config.keys()) print(f"\nDetected sections: {', '.join(sorted(sections))}") except Exception as e: print(f"Error during validation: {e}") sys.exit(1) if __name__ == "__main__": main()