ctfy.sdk.admin_resources.instances

client.admin.instances — live cluster-wide instance fleet (admin).

 1"""``client.admin.instances`` — live cluster-wide instance fleet (admin)."""
 2
 3from __future__ import annotations
 4
 5import builtins
 6from typing import Any
 7
 8from ctfy.sdk._helpers import PagedList, _extract_items, _raise_for_status
 9from ctfy.sdk.base import BaseHttpClient
10from ctfy.server.models import InstanceInfo
11
12
13class AdminInstancesResource:
14    """Every live instance cluster-wide, with forensic pcap / traffic reads.
15    Archived (terminal) records live on :class:`AdminRecordsResource`."""
16
17    def __init__(self, http: BaseHttpClient) -> None:
18        self._http = http
19
20    def stop_all(self) -> builtins.list[dict[str, Any]]:
21        """Cluster-wide tear-down: stop every running instance. Used for
22        maintenance windows. Returns one :class:`StopResponse` row per
23        instance that was stopped."""
24        resp = self._http.request("DELETE", "/instances")
25        _raise_for_status(resp)
26        rows: builtins.list[dict[str, Any]] = resp.json()
27        return rows
28
29    def stop(self, instance_id: str) -> dict[str, Any]:
30        """Stop one running instance, whoever owns it — the surgical
31        counterpart to :meth:`stop_all` for a wedged or abusive box.
32        The owner team's record is archived (logs captured) first."""
33        resp = self._http.request("DELETE", f"/admin/instances/{instance_id}")
34        _raise_for_status(resp)
35        row: dict[str, Any] = resp.json()
36        return row
37
38    def list(
39        self,
40        *,
41        node_id: str = "",
42        status: str = "",
43        offset: int = 0,
44        limit: int = 50,
45    ) -> PagedList[InstanceInfo]:
46        """Every live instance cluster-wide (no team-ownership filter)."""
47        params: dict[str, Any] = {"offset": offset, "limit": limit}
48        if node_id:
49            params["node_id"] = node_id
50        if status:
51            params["status"] = status
52        resp = self._http.request("GET", "/admin/instances", params=params)
53        _raise_for_status(resp)
54        return _extract_items(resp.json(), InstanceInfo)
55
56    def get(self, instance_id: str) -> InstanceInfo:
57        """Single live instance, admin view (any team's)."""
58        resp = self._http.request("GET", f"/admin/instances/{instance_id}")
59        _raise_for_status(resp)
60        return InstanceInfo.model_validate(resp.json())
61
62    def pcap(self, instance_id: str) -> bytes:
63        """Live tcpdump capture for a running instance. Returns empty
64        ``bytes`` when none exists (sidecar off, never ran) so callers
65        can persist conditionally without try/except."""
66        resp = self._http.request("GET", f"/admin/instances/{instance_id}/pcap")
67        if resp.status_code == 404:
68            return b""
69        _raise_for_status(resp)
70        return resp.content
71
72    def traffic(self, instance_id: str, *, cursor: str = "", limit: int = 0) -> dict[str, Any]:
73        """Live mitmproxy traffic for any running instance (admin twin
74        of :meth:`InstancesResource.traffic`, no ownership check).
75
76        Same ``{flows, cursor, truncated, capture}`` envelope and the same
77        polling contract; see the player's method for why an empty
78        cursor is not a value to send on every call.
79        """
80        params: dict[str, Any] = {"cursor": cursor} if cursor else {}
81        if limit > 0:
82            params["limit"] = limit
83        resp = self._http.request("GET", f"/admin/instances/{instance_id}/traffic", params=params)
84        _raise_for_status(resp)
85        data = resp.json()
86        return data if isinstance(data, dict) else {}
class AdminInstancesResource:
14class AdminInstancesResource:
15    """Every live instance cluster-wide, with forensic pcap / traffic reads.
16    Archived (terminal) records live on :class:`AdminRecordsResource`."""
17
18    def __init__(self, http: BaseHttpClient) -> None:
19        self._http = http
20
21    def stop_all(self) -> builtins.list[dict[str, Any]]:
22        """Cluster-wide tear-down: stop every running instance. Used for
23        maintenance windows. Returns one :class:`StopResponse` row per
24        instance that was stopped."""
25        resp = self._http.request("DELETE", "/instances")
26        _raise_for_status(resp)
27        rows: builtins.list[dict[str, Any]] = resp.json()
28        return rows
29
30    def stop(self, instance_id: str) -> dict[str, Any]:
31        """Stop one running instance, whoever owns it — the surgical
32        counterpart to :meth:`stop_all` for a wedged or abusive box.
33        The owner team's record is archived (logs captured) first."""
34        resp = self._http.request("DELETE", f"/admin/instances/{instance_id}")
35        _raise_for_status(resp)
36        row: dict[str, Any] = resp.json()
37        return row
38
39    def list(
40        self,
41        *,
42        node_id: str = "",
43        status: str = "",
44        offset: int = 0,
45        limit: int = 50,
46    ) -> PagedList[InstanceInfo]:
47        """Every live instance cluster-wide (no team-ownership filter)."""
48        params: dict[str, Any] = {"offset": offset, "limit": limit}
49        if node_id:
50            params["node_id"] = node_id
51        if status:
52            params["status"] = status
53        resp = self._http.request("GET", "/admin/instances", params=params)
54        _raise_for_status(resp)
55        return _extract_items(resp.json(), InstanceInfo)
56
57    def get(self, instance_id: str) -> InstanceInfo:
58        """Single live instance, admin view (any team's)."""
59        resp = self._http.request("GET", f"/admin/instances/{instance_id}")
60        _raise_for_status(resp)
61        return InstanceInfo.model_validate(resp.json())
62
63    def pcap(self, instance_id: str) -> bytes:
64        """Live tcpdump capture for a running instance. Returns empty
65        ``bytes`` when none exists (sidecar off, never ran) so callers
66        can persist conditionally without try/except."""
67        resp = self._http.request("GET", f"/admin/instances/{instance_id}/pcap")
68        if resp.status_code == 404:
69            return b""
70        _raise_for_status(resp)
71        return resp.content
72
73    def traffic(self, instance_id: str, *, cursor: str = "", limit: int = 0) -> dict[str, Any]:
74        """Live mitmproxy traffic for any running instance (admin twin
75        of :meth:`InstancesResource.traffic`, no ownership check).
76
77        Same ``{flows, cursor, truncated, capture}`` envelope and the same
78        polling contract; see the player's method for why an empty
79        cursor is not a value to send on every call.
80        """
81        params: dict[str, Any] = {"cursor": cursor} if cursor else {}
82        if limit > 0:
83            params["limit"] = limit
84        resp = self._http.request("GET", f"/admin/instances/{instance_id}/traffic", params=params)
85        _raise_for_status(resp)
86        data = resp.json()
87        return data if isinstance(data, dict) else {}

Every live instance cluster-wide, with forensic pcap / traffic reads. Archived (terminal) records live on AdminRecordsResource.

AdminInstancesResource(http: ctfy.sdk.base.BaseHttpClient)
18    def __init__(self, http: BaseHttpClient) -> None:
19        self._http = http
def stop_all(self) -> list[dict[str, typing.Any]]:
21    def stop_all(self) -> builtins.list[dict[str, Any]]:
22        """Cluster-wide tear-down: stop every running instance. Used for
23        maintenance windows. Returns one :class:`StopResponse` row per
24        instance that was stopped."""
25        resp = self._http.request("DELETE", "/instances")
26        _raise_for_status(resp)
27        rows: builtins.list[dict[str, Any]] = resp.json()
28        return rows

Cluster-wide tear-down: stop every running instance. Used for maintenance windows. Returns one StopResponse row per instance that was stopped.

def stop(self, instance_id: str) -> dict[str, typing.Any]:
30    def stop(self, instance_id: str) -> dict[str, Any]:
31        """Stop one running instance, whoever owns it — the surgical
32        counterpart to :meth:`stop_all` for a wedged or abusive box.
33        The owner team's record is archived (logs captured) first."""
34        resp = self._http.request("DELETE", f"/admin/instances/{instance_id}")
35        _raise_for_status(resp)
36        row: dict[str, Any] = resp.json()
37        return row

Stop one running instance, whoever owns it — the surgical counterpart to stop_all() for a wedged or abusive box. The owner team's record is archived (logs captured) first.

def list( self, *, node_id: str = '', status: str = '', offset: int = 0, limit: int = 50) -> ctfy.sdk._helpers.PagedList[ctfy.server.models.InstanceInfo]:
39    def list(
40        self,
41        *,
42        node_id: str = "",
43        status: str = "",
44        offset: int = 0,
45        limit: int = 50,
46    ) -> PagedList[InstanceInfo]:
47        """Every live instance cluster-wide (no team-ownership filter)."""
48        params: dict[str, Any] = {"offset": offset, "limit": limit}
49        if node_id:
50            params["node_id"] = node_id
51        if status:
52            params["status"] = status
53        resp = self._http.request("GET", "/admin/instances", params=params)
54        _raise_for_status(resp)
55        return _extract_items(resp.json(), InstanceInfo)

Every live instance cluster-wide (no team-ownership filter).

def get(self, instance_id: str) -> ctfy.server.models.InstanceInfo:
57    def get(self, instance_id: str) -> InstanceInfo:
58        """Single live instance, admin view (any team's)."""
59        resp = self._http.request("GET", f"/admin/instances/{instance_id}")
60        _raise_for_status(resp)
61        return InstanceInfo.model_validate(resp.json())

Single live instance, admin view (any team's).

def pcap(self, instance_id: str) -> bytes:
63    def pcap(self, instance_id: str) -> bytes:
64        """Live tcpdump capture for a running instance. Returns empty
65        ``bytes`` when none exists (sidecar off, never ran) so callers
66        can persist conditionally without try/except."""
67        resp = self._http.request("GET", f"/admin/instances/{instance_id}/pcap")
68        if resp.status_code == 404:
69            return b""
70        _raise_for_status(resp)
71        return resp.content

Live tcpdump capture for a running instance. Returns empty bytes when none exists (sidecar off, never ran) so callers can persist conditionally without try/except.

def traffic( self, instance_id: str, *, cursor: str = '', limit: int = 0) -> dict[str, typing.Any]:
73    def traffic(self, instance_id: str, *, cursor: str = "", limit: int = 0) -> dict[str, Any]:
74        """Live mitmproxy traffic for any running instance (admin twin
75        of :meth:`InstancesResource.traffic`, no ownership check).
76
77        Same ``{flows, cursor, truncated, capture}`` envelope and the same
78        polling contract; see the player's method for why an empty
79        cursor is not a value to send on every call.
80        """
81        params: dict[str, Any] = {"cursor": cursor} if cursor else {}
82        if limit > 0:
83            params["limit"] = limit
84        resp = self._http.request("GET", f"/admin/instances/{instance_id}/traffic", params=params)
85        _raise_for_status(resp)
86        data = resp.json()
87        return data if isinstance(data, dict) else {}

Live mitmproxy traffic for any running instance (admin twin of InstancesResource.traffic(), no ownership check).

Same {flows, cursor, truncated, capture} envelope and the same polling contract; see the player's method for why an empty cursor is not a value to send on every call.