91 lines
3.6 KiB
Python
91 lines
3.6 KiB
Python
"""Artifact materialization boundaries used by ARR delivery validation."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import Protocol
|
|
|
|
from arr_ingestion.contracts import IngestionError
|
|
|
|
|
|
class ArtifactStore(Protocol):
|
|
def materialize(self, object_key: str, destination: Path, max_bytes: int) -> None:
|
|
"""Copy one immutable object to a new private local file."""
|
|
|
|
|
|
class LocalObjectStore:
|
|
"""Controlled local implementation for tests and offline vertical slices."""
|
|
|
|
def __init__(self, root: Path) -> None:
|
|
self._root = root.resolve()
|
|
if not self._root.is_dir():
|
|
raise ValueError("object store root must be an existing directory")
|
|
|
|
def materialize(self, object_key: str, destination: Path, max_bytes: int) -> None:
|
|
source = self._root.joinpath(*object_key.split("/"))
|
|
try:
|
|
resolved = source.resolve(strict=True)
|
|
resolved.relative_to(self._root)
|
|
except (FileNotFoundError, OSError, ValueError):
|
|
raise IngestionError("ARTIFACT_NOT_FOUND", "delivery artifact is unavailable") from None
|
|
if source.is_symlink() or not resolved.is_file():
|
|
raise IngestionError("ARTIFACT_NOT_FOUND", "delivery artifact is unavailable")
|
|
destination.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
descriptor = os.open(destination, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
copied = 0
|
|
try:
|
|
with resolved.open("rb") as source_handle, os.fdopen(descriptor, "wb") as target:
|
|
descriptor = -1
|
|
while True:
|
|
chunk = source_handle.read(1024 * 1024)
|
|
if not chunk:
|
|
break
|
|
copied += len(chunk)
|
|
if copied > max_bytes:
|
|
raise IngestionError(
|
|
"ARTIFACT_TOO_LARGE", "delivery artifact exceeds its size limit"
|
|
)
|
|
target.write(chunk)
|
|
target.flush()
|
|
os.fsync(target.fileno())
|
|
except Exception:
|
|
destination.unlink(missing_ok=True)
|
|
raise
|
|
finally:
|
|
if descriptor >= 0:
|
|
os.close(descriptor)
|
|
|
|
|
|
class DirectoryArtifactPublisher:
|
|
"""Small helper for synthetic/local flows; production uses the OSS adapter."""
|
|
|
|
def __init__(self, root: Path) -> None:
|
|
self._root = root.resolve()
|
|
self._root.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
|
|
def put_file(self, source: Path, object_key: str) -> Path:
|
|
destination = self._root.joinpath(*object_key.split("/"))
|
|
try:
|
|
destination.resolve().relative_to(self._root)
|
|
except ValueError:
|
|
raise IngestionError("ARTIFACT_REFERENCE_INVALID", "object key is invalid") from None
|
|
destination.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
if destination.exists():
|
|
raise IngestionError("ARTIFACT_CONFLICT", "object key already exists")
|
|
descriptor = os.open(destination, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
try:
|
|
with source.open("rb") as source_handle, os.fdopen(descriptor, "wb") as target:
|
|
descriptor = -1
|
|
shutil.copyfileobj(source_handle, target, length=1024 * 1024)
|
|
target.flush()
|
|
os.fsync(target.fileno())
|
|
except Exception:
|
|
destination.unlink(missing_ok=True)
|
|
raise
|
|
finally:
|
|
if descriptor >= 0:
|
|
os.close(descriptor)
|
|
return destination
|