Preskoči na sadržaj

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

# upload_state → HTTP 401/403 → return (status, detail)
# Jer: isto kao #4 — telemetry, ne gate.

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