#!/usr/bin/env -S uv run --script --quiet # /// script # dependencies = [] # /// """ Validate DNS names against Virgo-Core naming convention. Naming Convention: --. Example: docker-01-nexus.spaceships.work Usage: ./validate_dns_naming.py docker-01-nexus.spaceships.work ./validate_dns_naming.py --file hostnames.txt ./validate_dns_naming.py --check-format docker-1-nexus.spaceships.work """ import argparse import re import sys from typing import Tuple class DNSNameValidator: """Validates DNS names against naming convention.""" # Pattern: --. PATTERN = r'^[a-z0-9-]+-\d{2}-[a-z0-9-]+\.[a-z0-9.-]+$' # Common service types KNOWN_SERVICES = { 'docker', 'k8s', 'proxmox', 'storage', 'db', 'network', 'app', 'vip', 'service', 'test', 'dev', 'staging', 'prod' } def __init__(self): self.pattern = re.compile(self.PATTERN) def validate(self, name: str) -> Tuple[bool, str, dict]: """ Validate DNS name. Returns: (is_valid, message, details_dict) """ # Basic pattern match if not self.pattern.match(name): return False, "Name doesn't match pattern: --.", {} # Split into components parts = name.split('.') if len(parts) < 2: return False, "Must include domain", {} hostname = parts[0] domain = '.'.join(parts[1:]) # Split hostname components = hostname.split('-') if len(components) < 3: return False, "Hostname must have at least 3 components: --", {} service = components[0] number = components[1] purpose = '-'.join(components[2:]) # Purpose can have hyphens # Validate number component (must be 2 digits) if not number.isdigit() or len(number) != 2: return False, f"Number component '{number}' must be exactly 2 digits (01-99)", {} # Additional checks warnings = [] # Check for known service type if service not in self.KNOWN_SERVICES: warnings.append(f"Service '{service}' not in known types (informational)") # Check for uppercase if name != name.lower(): return False, "Name must be lowercase only", {} # Check for invalid characters if not re.match(r'^[a-z0-9.-]+$', name): return False, "Name contains invalid characters (only a-z, 0-9, -, . allowed)", {} # Build details details = { 'service': service, 'number': number, 'purpose': purpose, 'domain': domain, 'warnings': warnings } message = "Valid" if warnings: message = f"Valid (with warnings: {', '.join(warnings)})" return True, message, details def validate_batch(self, names: list) -> dict: """ Validate multiple names. Returns: { 'valid': [(name, details), ...], 'invalid': [(name, reason), ...] } """ results = {'valid': [], 'invalid': []} for name in names: name = name.strip() if not name or name.startswith('#'): continue is_valid, message, details = self.validate(name) if is_valid: results['valid'].append((name, details)) else: results['invalid'].append((name, message)) return results def print_validation_result(name: str, is_valid: bool, message: str, details: dict, verbose: bool = False): """Print formatted validation result.""" status = "āœ“" if is_valid else "āœ—" print(f"{status} {name}: {message}") if verbose and is_valid and details: print(f" Service: {details.get('service')}") print(f" Number: {details.get('number')}") print(f" Purpose: {details.get('purpose')}") print(f" Domain: {details.get('domain')}") warnings = details.get('warnings', []) if warnings: print(f" Warnings:") for warning in warnings: print(f" - {warning}") def main(): parser = argparse.ArgumentParser( description="Validate DNS names against Virgo-Core naming convention", epilog="Pattern: --.\nExample: docker-01-nexus.spaceships.work" ) parser.add_argument( "name", nargs="?", help="DNS name to validate" ) parser.add_argument( "--file", help="File containing DNS names (one per line)" ) parser.add_argument( "--verbose", "-v", action="store_true", help="Show detailed component breakdown" ) parser.add_argument( "--check-format", action="store_true", help="Only check format, don't suggest corrections" ) args = parser.parse_args() validator = DNSNameValidator() # Batch mode from file if args.file: try: with open(args.file, 'r') as f: names = f.readlines() except IOError as e: print(f"āŒ Failed to read file: {e}", file=sys.stderr) sys.exit(1) results = validator.validate_batch(names) print(f"\nšŸ“Š Validation Results:") print(f" Valid: {len(results['valid'])}") print(f" Invalid: {len(results['invalid'])}") if results['invalid']: print(f"\nāœ— Invalid Names:") for name, reason in results['invalid']: print(f" {name}") print(f" Reason: {reason}") if results['valid']: print(f"\nāœ“ Valid Names:") for name, details in results['valid']: print(f" {name}") if args.verbose: print(f" Service: {details['service']}, Number: {details['number']}, Purpose: {details['purpose']}") sys.exit(0 if len(results['invalid']) == 0 else 1) # Single name mode if not args.name: print("āŒ Provide a DNS name or use --file", file=sys.stderr) parser.print_help() sys.exit(1) is_valid, message, details = validator.validate(args.name) print_validation_result(args.name, is_valid, message, details, args.verbose) # Provide suggestions for common mistakes if not is_valid and not args.check_format: print(f"\nšŸ’” Common Issues:") print(f" - Use lowercase only: {args.name.lower()}") print(f" - Use hyphens, not underscores: {args.name.replace('_', '-')}") print(f" - Number must be 2 digits: docker-1-app → docker-01-app") print(f" - Pattern: --.") print(f" - Example: docker-01-nexus.spaceships.work") sys.exit(0 if is_valid else 1) if __name__ == "__main__": main()