Zašto VendorAuthError postoji kao SystemExit(2)?¶
TL;DR
VendorAuthError (u ci/_errors.py) je SystemExit(2) subclass
koji svaki vendor api/api.py raise-uje na HTTP 401/403. Exit
kod 2 razlikuje "secrets misconfigured, retry futile" od
generičkog exit 1 (transient fail, retry might help). 5
dokumentovanih izuzetaka od pravila.
Koncept¶
# ci/_errors.py
class VendorAuthError(SystemExit):
def __init__(self, vendor: str, code: int, detail: str):
super().__init__(2)
self.vendor = vendor
self.code = code
self.detail = detail
# ci/coolify/api/api.py
if resp.status in (401, 403):
raise VendorAuthError("coolify", resp.status, body)
Exit code 2 = "ne pokušavaj ponovo, secret je pogrešan". Pipeline se odmah zaustavlja; operater mora ručno popraviti var.
Zašto exit 2, ne exit 1¶
| Exit | Značenje | Retry ponašanje |
|---|---|---|
| 0 | Uspjeh | n/a |
| 1 | Transient fail (mreža, 5xx, parse) | retry might help |
| 2 | Auth fail (401/403) | NE retry |
| 3+ | Custom | zavisi |
Bitbucket pipeline prati exit kod. Ako deploy fail-uje sa 2, "deploy" step je fail-closed i signalizira operateru: "ovo je tvoj posao, ne CI-ja". Exit 1 bi značilo "možda pokušaj opet".
Pet dokumentovanih izuzetaka¶
Pravilo: HTTP 401/403 → raise VendorAuthError. Pet izuzetaka
od pravila, svi dokumentovani u ci/_errors.py:
1. Slack — webhook auth¶
# hooks.slack.com 401/403 → return False (NE raise)
# Jer: Slack je observability, ne deploy state.
# Notify failure NE SMIJE abortati pipeline koji je upravo deploy-ovao.
ci/slack/api/api.py vraća False na 401/403. Caller odlučuje
fallback (obično log + nastavi).
2. AWS / ECS — boto3 typed ClientError¶
# boto3 → ClientError('AccessDeniedException')
# Čuva se u state kao 'register-failed:AccessDeniedException'
# Jer: nema čist 1:1 između boto3 kodova i HTTP 401/403.
# Operater treba PRECIZNU dijagnostiku.
ECS auth fail-ovi se čuvaju u ci-state.json sa punim boto3
kodom (vidi 02-verbs/03-deploy-ecs.md).
3. Registry (OCI Registry V2) — anonymous¶
# /token?scope=... → 403 (registar odbija anonymous token)
# → return None (NE raise)
# Jer: anonymous public registry nema "auth wrong" koncept.
# Caller (scan_smart) odlučuje fail-closed lokalno.
4. test-results ingest — telemetry-only¶
# upload_junit → HTTP 401/403 → return (status, detail)
# Jer: telemetry-only upload NE SMIJE abortati test/lint step.
Isti contract i za ci/upload/api/api.py (pipeline-state ingest).
5. pipeline-state ingest — telemetry-only¶
Dodavanje novog vendora¶
# Standardni pattern (5 od 5 standardnih vendora)
from ci._errors import VendorAuthError
if resp.status in (401, 403):
raise VendorAuthError("myvendor", resp.status, body)
# Izuzeci: dokumentuj u ci/_errors.py zašto NE raise-uješ.
# Bez dokumentacije → CODE REVIEW fail.
Svaki novi vendor koji odstupa mora biti dodat u ci/_errors.py
"Five documented exceptions" listu PRIJE nego se razilazi.
Vidi i¶
v7.md§2 — "Šta ako ne radi" (auth sekcija)ci/_errors.py—VendorAuthErrorsourceci/CLAUDE.md(root) — "VendorAuthError contract" sekcija sa 5 izuzetaka02-verbs/03-deploy-ecs.md— boto3 izuzetak deep-dive04-incidents/01-build-failed.md— oncall auth triage