- models: DomainConfig, ServiceConfig, DiagnosticReport (single source of truth) - interfaces: DIP & ISP contracts for detector, generator, executor, doctor, and migrator - detector: Smart auto-detection for modular (config/wsgi.py), traditional, and flat layouts - service_generator: Systemd service generation with --umask 007 and security sandboxing - executor: Linux command execution with dry-run support - nginx_manager: HestiaCP template installation and validation - migrator: Automated migration and repair of existing deployments - doctor: Comprehensive post-deployment health check engine
90 lines
2.9 KiB
Python
90 lines
2.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Abstract interfaces (contracts) for the deployment engine.
|
|
|
|
Applying the **Interface Segregation Principle** (ISP) and
|
|
**Dependency Inversion Principle** (DIP): every high-level orchestrator
|
|
depends on these thin abstractions — never on concrete implementations.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from pathlib import Path
|
|
from typing import Optional, Tuple
|
|
|
|
from core.models import (
|
|
DiagnosticReport,
|
|
DjangoProjectInfo,
|
|
DomainConfig,
|
|
ServiceConfig,
|
|
)
|
|
|
|
|
|
class IDjangoDetector(ABC):
|
|
"""Inspects a Django project directory and returns structured metadata."""
|
|
|
|
@abstractmethod
|
|
def detect(self, web_dir: Path, project_hint: Optional[str] = None) -> DjangoProjectInfo:
|
|
"""Analyse *web_dir* and return a populated ``DjangoProjectInfo``."""
|
|
|
|
|
|
class IServiceGenerator(ABC):
|
|
"""Renders systemd service unit content from a ``ServiceConfig``."""
|
|
|
|
@abstractmethod
|
|
def generate(self, config: ServiceConfig) -> str:
|
|
"""Return the full text of a systemd ``.service`` unit file."""
|
|
|
|
|
|
class ISystemExecutor(ABC):
|
|
"""Abstraction over OS-level command execution."""
|
|
|
|
@abstractmethod
|
|
def run(self, command: str, check: bool = True) -> Tuple[int, str, str]:
|
|
"""Execute *command* and return ``(return_code, stdout, stderr)``."""
|
|
|
|
@abstractmethod
|
|
def write_file(self, path: Path, content: str, mode: int = 0o644) -> None:
|
|
"""Write *content* to *path* with the given POSIX *mode*."""
|
|
|
|
@abstractmethod
|
|
def file_exists(self, path: Path) -> bool:
|
|
"""Return ``True`` if *path* exists on the filesystem."""
|
|
|
|
@abstractmethod
|
|
def backup_file(self, path: Path) -> Optional[Path]:
|
|
"""Create a timestamped backup of *path* and return the backup path."""
|
|
|
|
|
|
class INginxManager(ABC):
|
|
"""Manages HestiaCP NGINX proxy template installation."""
|
|
|
|
@abstractmethod
|
|
def install_templates(self, tpl_source: Path, stpl_source: Path) -> None:
|
|
"""Copy templates to the HestiaCP template directory and set permissions."""
|
|
|
|
@abstractmethod
|
|
def rebuild_domain(self, domain_config: DomainConfig) -> None:
|
|
"""Force-rebuild the NGINX configuration for a domain."""
|
|
|
|
|
|
class IProjectMigrator(ABC):
|
|
"""Upgrades / repairs a previously deployed project in-place."""
|
|
|
|
@abstractmethod
|
|
def needs_migration(self, domain_config: DomainConfig) -> bool:
|
|
"""Return ``True`` if the existing deployment requires migration."""
|
|
|
|
@abstractmethod
|
|
def migrate(self, domain_config: DomainConfig) -> DiagnosticReport:
|
|
"""Perform the migration / repair and return a diagnostic report."""
|
|
|
|
|
|
class IDeploymentDoctor(ABC):
|
|
"""Runs post-deployment health checks."""
|
|
|
|
@abstractmethod
|
|
def diagnose(self, domain_config: DomainConfig) -> DiagnosticReport:
|
|
"""Run all health checks and return a ``DiagnosticReport``."""
|