Files

91 lines
3.0 KiB
Python

"""Docker Compose validation functionality."""
from __future__ import annotations
import logging
import shutil
import subprocess
import tempfile
from pathlib import Path
from ...core.validation import KindValidationFailure, KindValidationResult
logger = logging.getLogger(__name__)
class ComposeDockerValidator:
"""Kind-specific validator backed by Docker Compose."""
validator_name = "docker compose config"
unavailable_message = "Required command is unavailable: docker compose"
def __init__(self, verbose: bool = False) -> None:
self.verbose = verbose
self._available: bool | None = None
def is_available(self) -> bool:
"""Check whether Docker Compose is available locally."""
if self._available is not None:
return self._available
if shutil.which("docker") is None:
self._available = False
return self._available
result = subprocess.run(
["docker", "compose", "version"],
capture_output=True,
text=True,
check=False,
)
self._available = result.returncode == 0
return self._available
def validate_rendered_files(self, rendered_files: dict[str, str], _case_name: str) -> KindValidationResult:
"""Validate rendered Compose files with Docker Compose."""
result = KindValidationResult(validator=self.validator_name, available=self.is_available())
if not result.available:
result.details.append(self.unavailable_message)
return result
compose_files = _find_compose_files(rendered_files)
if not compose_files:
result.warnings.append("No Docker Compose files found")
return result
for filename, content in compose_files:
failure = self._validate_compose_content(filename, content)
if failure is not None:
result.failures.append(failure)
return result
def _validate_compose_content(self, filename: str, content: str) -> KindValidationFailure | None:
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp_file:
tmp_file.write(content)
tmp_path = tmp_file.name
try:
result = subprocess.run(
["docker", "compose", "-f", tmp_path, "config", "--quiet"],
capture_output=True,
text=True,
check=False,
)
finally:
Path(tmp_path).unlink(missing_ok=True)
if result.returncode == 0:
return None
message = result.stderr.strip() or result.stdout.strip() or "Docker Compose validation failed"
return KindValidationFailure(file_path=filename, validator=self.validator_name, message=message)
def _find_compose_files(rendered_files: dict[str, str]) -> list[tuple[str, str]]:
return [
(filename, content)
for filename, content in rendered_files.items()
if filename.endswith(("compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"))
]