Source code for infrahouse_core.validation
"""Input validation utilities for AWS resource identifiers."""
import re
from logging import getLogger
from typing import Optional
LOG = getLogger(__name__)
[docs]
def validate_instance_id(instance_id: Optional[str]) -> None:
"""
Validate EC2 instance ID format.
:param instance_id: Instance ID to validate
:type instance_id: str or None
:raises ValueError: If instance_id is invalid
"""
if not instance_id:
return # None or empty string is valid - will be read from metadata
if not isinstance(instance_id, str):
raise ValueError(f"instance_id must be a string, got {type(instance_id).__name__}")
if not re.match(r"^i-[0-9a-f]{8,17}$", instance_id):
raise ValueError(f"Invalid instance ID format: '{instance_id}'. " f"Expected format: i-xxxxxxxxxxxxxxxxx")
[docs]
def validate_role_arn(role_arn: Optional[str]) -> None:
"""
Validate IAM role ARN format.
:param role_arn: Role ARN to validate
:type role_arn: str or None
:raises ValueError: If role_arn is invalid
"""
if not role_arn:
return # None or empty string is valid - no role assumption
if not isinstance(role_arn, str):
raise ValueError(f"role_arn must be a string, got {type(role_arn).__name__}")
# AWS ARN format: arn:partition:service:region:account-id:resource
# For IAM roles: arn:aws:iam::123456789012:role/RoleName
# Also support arn:aws-us-gov:iam:: for GovCloud
if not re.match(r"^arn:aws(-[a-z-]+)?:iam::\d{12}:role/.+$", role_arn):
raise ValueError(
f"Invalid role ARN format: '{role_arn}'. " f"Expected format: arn:aws:iam::123456789012:role/RoleName"
)
[docs]
def validate_region(region: Optional[str]) -> None:
"""
Validate AWS region name format.
:param region: AWS region name to validate
:type region: str or None
:raises ValueError: If region is invalid
"""
if region is None:
return # None is valid - will use default resolution
if not isinstance(region, str):
raise ValueError(f"region must be a string, got {type(region).__name__}")
# AWS region format: us-east-1, eu-west-2, ap-southeast-1, us-gov-west-1, etc.
if not re.match(r"^[a-z]{2}(-[a-z]+)+-\d{1}$", region):
raise ValueError(f"Invalid region format: '{region}'. " f"Expected format: us-east-1, eu-west-2, etc.")
[docs]
def validate_dns_name(dns_name: Optional[str]) -> None:
"""
Validate DNS zone name format.
:param dns_name: DNS name to validate
:type dns_name: str or None
:raises ValueError: If dns_name is invalid
"""
if dns_name is None:
return
if not isinstance(dns_name, str):
raise ValueError(f"dns_name must be a string, got {type(dns_name).__name__}")
# Basic DNS name validation
if not dns_name or len(dns_name) > 255:
raise ValueError(f"Invalid DNS name length: '{dns_name}'")
# Allow trailing dot
name = dns_name.rstrip(".")
# Check labels
labels = name.split(".")
if not labels:
raise ValueError(f"Invalid DNS name: '{dns_name}'")
for label in labels:
if not label or len(label) > 63:
raise ValueError(f"Invalid DNS label in '{dns_name}': '{label}'")
if not re.match(r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", label, re.IGNORECASE):
raise ValueError(f"Invalid DNS label in '{dns_name}': '{label}'")
[docs]
def validate_zone_id(zone_id: Optional[str]) -> None:
"""
Validate Route53 hosted zone ID format.
:param zone_id: Zone ID to validate
:type zone_id: str or None
:raises ValueError: If zone_id is invalid
"""
if zone_id is None:
return
if not isinstance(zone_id, str):
raise ValueError(f"zone_id must be a string, got {type(zone_id).__name__}")
# Zone IDs are alphanumeric, may have /hostedzone/ prefix
zone_id_clean = zone_id.replace("/hostedzone/", "")
if not re.match(r"^[A-Z0-9]{1,32}$", zone_id_clean):
raise ValueError(
f"Invalid zone ID format: '{zone_id}'. " f"Expected alphanumeric string (e.g., Z1234567890ABC)"
)