Skip to content

Commit ea22a7c

Browse files
pmakanju-e2be2b-bot[bot]
authored andcommitted
feat(api): create api routes to ban teams and apply project blocks
GitOrigin-RevId: ed1635b7f58003fa036312715980577484d8c6ce
1 parent 51083a8 commit ea22a7c

22 files changed

Lines changed: 4845 additions & 582 deletions

‎.mockery.yaml‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,24 @@ packages:
8787
filename: mockfeatureflagsclient.go
8888
pkgname: handlersmocks
8989
structname: MockFeatureFlagsClient
90+
91+
github.com/e2b-dev/infra/packages/api/internal/team:
92+
interfaces:
93+
teamCache:
94+
config:
95+
dir: packages/api/internal/team/mocks
96+
filename: mockteamcache.go
97+
pkgname: teammocks
98+
structname: MockTeamCache
99+
sandboxKiller:
100+
config:
101+
dir: packages/api/internal/team/mocks
102+
filename: mocksandboxkiller.go
103+
pkgname: teammocks
104+
structname: MockSandboxKiller
105+
buildCanceller:
106+
config:
107+
dir: packages/api/internal/team/mocks
108+
filename: mockbuildcanceller.go
109+
pkgname: teammocks
110+
structname: MockBuildCanceller

‎docs/ARCHITECTURE.md‎

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -305,16 +305,21 @@ retries.
305305
a delivery at or below the recorded revision is dropped and still answers 204. The ledger and the
306306
values advance in one transaction, so a revision is never recorded without the values it
307307
admitted.
308+
- `PUT .../block` applies a revision-fenced block state, the same write as dashboard-api's
309+
`PUT /v1/management/projects/{projectID}/block` (see Dashboard API below): the
310+
`projection.project_blocks` revision and `teams.is_blocked` / `blocked_reason` commit together,
311+
and every accepted delivery, a dropped one included, evicts the team's auth cache.
308312
- `DELETE /unstable/management/projects/{projectID}` soft-deletes the project, described below.
309313

310-
Both revision fences are the target's for the same reason: the caller can only fence what it
314+
The revision fences are the target's for the same reason: the caller can only fence what it
311315
sends, so two deliveries in flight arrive in whichever order the network gives them, and the older
312-
one has to be refused where it lands. The project, membership and limits writes live in
316+
one has to be refused where it lands. The project, membership, limits and block writes live in
313317
`internal/management` with their post-commit cache evictions rather than in the handlers.
314318

315-
A limits delivery may carry `decided_at`, when the caller decided that revision; the ledger row
316-
stores it beside the revision. Each delivery the ledger accepts records the seconds since then in
317-
`dashboard-api.management.apply_lag` with `projection` `project_limits`; one without `decided_at`
319+
A limits or block delivery may carry `decided_at`, when the caller decided that revision; the
320+
ledger row stores it beside the revision. Each delivery the ledger accepts records the seconds
321+
since then in `dashboard-api.management.apply_lag` with `projection` `project_limits` or
322+
`project_blocks`; one without `decided_at`
318323
counts in `dashboard-api.management.origin_unknown` instead, never as zero lag. Dropped, refused
319324
and failed deliveries record nothing. The names keep the dashboard-api prefix because dashboard-api
320325
records its block deliveries under the same two metrics, so one alert and one dashboard read both.
@@ -341,12 +346,31 @@ deletes memberships, keys and envs created since (a sandbox that paused before t
341346
it leaves a snapshot); and enqueues the teardown again, which is a no-op while one is still open.
342347

343348
Afterwards the API's deleted-project middleware, running after authentication, answers 404 to every
344-
non-GET route under `/unstable/management/` that names `{projectID}`: the upsert, member, limits
345-
and settings writes. The DELETE is exempt so a retry still reaches it. dashboard-api guards its own
349+
non-GET route under `/unstable/management/` that names `{projectID}`: the upsert, member, limits,
350+
block and settings writes. The DELETE is exempt so a retry still reaches it. dashboard-api guards its own
346351
management and admin writes the same way. Shared auth refuses a membership or API key still left
347352
for the project with 403, and the API leaves the project out of a user's team list. Volumes, builds
348353
and stored artifacts are not reclaimed.
349354

355+
#### Team bans (`/unstable/admin/teams`)
356+
357+
`PUT /unstable/admin/teams/{teamID}/ban` sets `teams.is_banned`, evicts the team's auth cache so
358+
its API keys stop authenticating, then kills its running sandboxes and cancels its builds through
359+
the same orchestrator and template-manager calls as `POST /admin/teams/{teamID}/sandboxes/kill`
360+
and `.../builds/cancel`. The ban commits first: a kill or cancel step that fails is reported in
361+
the 200 response and does not undo it. The work is detached from the request, so a caller that
362+
disconnects does not stop it. Each team's kill and cancel run side by side, and all of a request's
363+
kills and cancellations share a 25-second budget; a step the budget cuts short is reported as
364+
failed with what it counted so far. A build delete already
365+
under way keeps its own 30 seconds, so a request ends within about 55 seconds. A failed cache
366+
eviction answers 500, so the caller's retry repeats the ban and its containment; banning a banned
367+
team contains it again. `DELETE .../ban` clears the flag and evicts the cache.
368+
`POST /unstable/admin/teams/bans` takes up to 100 teams. It stores every ban first, five teams at
369+
a time, then contains the banned teams, five at a time under the one budget, and sorts each team
370+
into banned, not found or failed; one team's failure does not stop the others. A deleted project
371+
answers 404 to the single-team routes and counts as not found in the bulk route. The logic lives
372+
in `internal/team`.
373+
350374
### Orchestrator (`packages/orchestrator`)
351375

352376
A single Go binary running on every sandbox node (as root). `ORCHESTRATOR_SERVICES` selects its
@@ -610,7 +634,8 @@ state. The `projection.project_blocks` revision and `public.teams.is_blocked` /
610634
`blocked_reason` commit together. Older or duplicate revisions leave stored state
611635
unchanged. Every accepted delivery invalidates the team auth cache; eviction
612636
failures return an error so retries can finish invalidation after commit. The API
613-
uses this state when admitting billable work. It is the only route that writes the
637+
uses this state when admitting billable work. It and the API's copy at
638+
`/unstable/management/projects/{projectID}/block` are the only routes that write the
614639
team's block state.
615640

616641
Block deliveries may carry `decided_at`, when the caller decided that

0 commit comments

Comments
 (0)