Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:00:21 +08:00
commit 26377bd9be
20 changed files with 8845 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
#!/usr/bin/env -S uv run --script --quiet
# /// script
# dependencies = []
# ///
"""
Validate DNS names against Virgo-Core naming convention.
Naming Convention: <service>-<NN>-<purpose>.<domain>
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: <service>-<NN>-<purpose>.<domain>
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: <service>-<NN>-<purpose>.<domain>", {}
# 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>-<NN>-<purpose>", {}
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: <service>-<NN>-<purpose>.<domain>\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: <service>-<NN>-<purpose>.<domain>")
print(f" - Example: docker-01-nexus.spaceships.work")
sys.exit(0 if is_valid else 1)
if __name__ == "__main__":
main()