Vendor API contract (HTTP-call invariant)¶
TL;DR
Svaki spoljni HTTP poziv živi u ci/<vendor>/api/api.py.
Orchestrator skripte NE drže urllib.request.Request — samo
typed metode. User-Agent, timeout, auth header, error handling
— sve centralizirano. 5 izuzetaka (dokumentovana u
ci/_errors.py).
Pravilo¶
# ❌ Zabranjeno u orchestrator skripti:
import urllib.request
req = urllib.request.Request("https://coolify.example/api/...", headers={...})
resp = urllib.request.urlopen(req, timeout=30)
# ✅ Ispravno — typed metoda:
from ci.coolify.api import api
result = api.set_env(uuid, key, value)
Zašto:
- Cloudflare WAF vraća HTTP 403 +
error code: 1010za defaultPython-urllib/X.YUser-Agent. CI-artifacts koristici-<vendor>/<ver>UA. - Timeout mora biti eksplicitan; default socket timeout je predug za CI.
- Auth header construction jednom, ne 20 puta.
- Observability jedno mjesto.
Šta api/api.py MORA imati¶
# Standardni pattern (5 od 5 standardnih vendora)
from ci._errors import VendorAuthError
def _request(method, path, **kwargs):
headers = {"User-Agent": "ci-coolify/1.0", **kwargs.pop("headers", {})}
if COOLIFY_API_TOKEN:
headers["Authorization"] = f"Bearer {COOLIFY_API_TOKEN}"
resp = urllib.request.urlopen(
urllib.request.Request(url + path, headers=headers, method=method, **kwargs),
timeout=30,
)
if resp.status in (401, 403):
raise VendorAuthError("coolify", resp.status, resp.read().decode())
return resp
# Typed method:
def set_env(uuid: str, key: str, value: str) -> SetEnvResponse:
resp = _request("POST", f"/applications/{uuid}/envs", json={...})
return SetEnvResponse.from_dict(json.loads(resp.read()))
Obavezno:
- ✅
User-Agentheader (ci-<vendor>/<ver>) - ✅ Eksplicitan timeout (NE default socket)
- ✅ Frozen dataclass za response (vidi
ci/coolify/api/types.py) - ✅ Auth iz
config.py, NE inline - ✅
VendorAuthErrorna 401/403 (default) - ✅
return Noneili(None, msg)tuple na ostale 4xx/5xx/network
Pet dokumentovanih izuzetaka¶
| Vendor | Zašto izuzetak |
|---|---|
| Slack | Webhook auth 401/403 → return False. Observability, ne gate. |
| AWS/ECS | boto3 typed ClientError umjesto VendorAuthError. Čuva boto3 kod u state. |
| Registry (OCI V2) | Anonymous public; HTTP 403 = "registry refused anonymous token". Caller odlučuje fail-closed. |
| test-results ingest | Telemetry-only; HTTP 401/403 → (status, detail) tuple. NE aborta. |
| pipeline-state ingest | Isto kao test-results. |
Svaki novi vendor koji odstupa mora biti dodat u ci/_errors.py
"Five documented exceptions" PRIJE nego se razilazi.
SDK vendor adaptation¶
Dva vendora koriste SDK umjesto raw urllib:
ci/aws— boto3ci/langfuse_prompts— langfuse SDK
Pravilo: SDK import + svi SDK pozivi žive u ci/<vendor>/api/api.py
(jedan external-call site). Orchestratori import-uju samo
typed metode, nikad import boto3 / import langfuse.
Oba su LAZY-import-ovana (from ci.aws.api import api) tako da
syntax/pyright/pytest rade bez SDK instaliranog; fail-loud sa
typed *NotInstalled na prvi pravi poziv.
Binarni installer-i (NE REST vendor)¶
ci/gitleaks/, ci/squawk/, ci/git_cliff/ — instaliraju pinned
binarni, NE pozivaju REST. Nemaju config.py. Ima api/api.py ali
je thin wrapper oko ANONYMOUS download-a.
Vidi i¶
01-onboarding/04-vendor-auth-contract.md— VendorAuthError deep-diveci/_errors.py— 5 izuzetakaci/CLAUDE.md(root) — "Vendor api/api.py requirements" + "HTTP-call invariant (NO EXCEPTIONS)"