|
14 | 14 | VELA_KEYCLOAK_CLIENT_SECRET = os.getenv("VELA_KEYCLOAK_CLIENT_SECRET", "controller-secret") |
15 | 15 | BRANCH_TIMEOUT_SEC = int(os.getenv("BRANCH_TIMEOUT_SEC", "900")) |
16 | 16 |
|
17 | | -_POLL_INTERVAL = 10 |
| 17 | +_POLL_INTERVAL = 1 |
18 | 18 |
|
19 | 19 |
|
20 | 20 | class _KeycloakAuth(httpx.Auth): |
@@ -51,20 +51,41 @@ def auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Re |
51 | 51 | yield request |
52 | 52 |
|
53 | 53 |
|
54 | | -def wait_for_status(client: httpx.Client, url: str, expected: str, timeout: int = BRANCH_TIMEOUT_SEC) -> dict: |
55 | | - """Poll GET url every 10s until response.json()["status"] == expected or timeout.""" |
| 54 | +def wait_for_status( |
| 55 | + client: httpx.Client, |
| 56 | + url: str, |
| 57 | + expected: list[str], |
| 58 | + timeout: int = BRANCH_TIMEOUT_SEC, |
| 59 | +) -> dict: |
| 60 | + """Poll GET url every 10s until the resource progresses through ``expected``. |
| 61 | +
|
| 62 | + ``expected`` is an ordered list of statuses. Each status observed must |
| 63 | + appear at or after the current position in the list; going backward raises |
| 64 | + immediately. Statuses not in the list are treated as unknown intermediate |
| 65 | + states and ignored. ERROR always raises immediately. The final entry in |
| 66 | + ``expected`` is the success condition. |
| 67 | + """ |
56 | 68 | deadline = time.monotonic() + timeout |
| 69 | + idx = 0 |
| 70 | + final = expected[-1] |
57 | 71 | while True: |
58 | 72 | r = client.get(url, timeout=30) |
59 | 73 | r.raise_for_status() |
60 | 74 | data = r.json() |
61 | 75 | current = data.get("status") |
62 | | - if current == expected: |
63 | | - return data |
64 | 76 | if current == "ERROR": |
65 | | - raise RuntimeError(f"Resource entered ERROR state while waiting for status={expected!r}") |
| 77 | + raise RuntimeError(f"Resource entered ERROR state while waiting for status={final!r}") |
| 78 | + try: |
| 79 | + idx = expected.index(current, idx) |
| 80 | + except ValueError: |
| 81 | + if current in expected[:idx]: |
| 82 | + raise RuntimeError(f"Status {current!r} regressed; expected one of {expected[idx:]}") from None |
| 83 | + # Unknown intermediate state; keep waiting |
| 84 | + else: |
| 85 | + if current == final: |
| 86 | + return data |
66 | 87 | if time.monotonic() >= deadline: |
67 | | - raise TimeoutError(f"Timed out waiting for status={expected!r}; last status={current!r}") |
| 88 | + raise TimeoutError(f"Timed out waiting for status={final!r}; last status={current!r}") |
68 | 89 | time.sleep(_POLL_INTERVAL) |
69 | 90 |
|
70 | 91 |
|
@@ -152,7 +173,7 @@ def _factory(org_id: ULID, project_id: ULID, name: str, **overrides: object) -> |
152 | 173 | wait_for_status( |
153 | 174 | client, |
154 | 175 | f"organizations/{org_id}/projects/{project_id}/branches/{uid}/", |
155 | | - "ACTIVE_HEALTHY", |
| 176 | + ["ACTIVE_HEALTHY"], |
156 | 177 | BRANCH_TIMEOUT_SEC, |
157 | 178 | ) |
158 | 179 | created.append((org_id, project_id, uid)) |
|
0 commit comments