Preskoči na sadržaj

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: 1010 za default Python-urllib/X.Y User-Agent. CI-artifacts koristi ci-<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-Agent header (ci-<vendor>/<ver>)
  • ✅ Eksplicitan timeout (NE default socket)
  • ✅ Frozen dataclass za response (vidi ci/coolify/api/types.py)
  • ✅ Auth iz config.py, NE inline
  • VendorAuthError na 401/403 (default)
  • return None ili (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 — boto3
  • ci/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