from __future__ import annotations from datetime import datetime, timezone from typing import Optional from sqlmodel import Field, SQLModel def _now() -> datetime: return datetime.now(timezone.utc) #: How to reach a private repository. "token" is an HTTPS username + personal #: access token; "ssh" is a private key. AUTH_TYPES = ["none", "token", "ssh"] class GitSource(SQLModel, table=True): """A Git repository that a stack's files are deployed from. The repository is the source of truth: a sync overwrites the stack's files with what the repo says, which is the whole point of GitOps and also the thing to be careful about. Only files the repo has ever provided are touched — see ``services/git_service.py`` — so the data directories compose creates inside a stack folder are never at risk. """ id: Optional[int] = Field(default=None, primary_key=True) stack_id: str = Field(index=True, unique=True) url: str branch: str = "main" #: Subdirectory inside the repository holding the compose file. Empty means #: the repository root, which is the common case for one-stack repos. subdir: str = "" auth_type: str = "none" username: Optional[str] = None #: Encrypted: the access token, or the SSH private key. secret: Optional[str] = None #: Run `compose up -d` after a sync that actually changed something. auto_deploy: bool = True #: Poll the repository this often. None means only manual syncs and webhooks. poll_interval_minutes: Optional[int] = None #: Shared secret for the webhook endpoint (HMAC, or GitLab's token header). webhook_secret: str = "" #: JSON list of the paths the last sync wrote, relative to the stack folder. #: The only files a later sync is allowed to delete. managed_files: str = "[]" last_commit: Optional[str] = None last_synced_at: Optional[datetime] = None last_error: Optional[str] = None created_at: datetime = Field(default_factory=_now) updated_at: datetime = Field(default_factory=_now) # --- API schemas --- class GitSourceWrite(SQLModel): url: str branch: str = "main" subdir: str = "" auth_type: str = "none" username: Optional[str] = None #: Omitted on update keeps the stored one. secret: Optional[str] = None auto_deploy: bool = True poll_interval_minutes: Optional[int] = None class GitSourceRead(SQLModel): stack_id: str url: str branch: str subdir: str auth_type: str username: Optional[str] has_secret: bool auto_deploy: bool poll_interval_minutes: Optional[int] webhook_url: str last_commit: Optional[str] last_synced_at: Optional[datetime] last_error: Optional[str] managed_file_count: int class SyncResult(SQLModel): changed: bool commit: Optional[str] = None written: list[str] = [] removed: list[str] = [] deployed: bool = False detail: Optional[str] = None