|
# retoor <retoor@molodetz.nl>
|
|
|
|
from .._shared import endpoint, field
|
|
|
|
GROUP = {
|
|
"slug": "containers",
|
|
"title": "Container Manager",
|
|
"admin": True,
|
|
"intro": """
|
|
# Container Manager
|
|
|
|
Run supervised container instances for a project. There is no in-app image building: every instance
|
|
runs one shared prebuilt image (`ppy:latest`) with the project's workspace mounted at `/app`. Every
|
|
endpoint is **administrator only** (docker socket access is root-equivalent). Mutations flip desired
|
|
state; a single reconciler converges containers to it.
|
|
""",
|
|
"endpoints": [
|
|
endpoint(
|
|
id="containers-page",
|
|
method="GET",
|
|
path="/projects/{project_slug}/containers",
|
|
title="Container manager page",
|
|
summary="The admin per-project container manager UI (instance creation and lifecycle). Returns 404 for an administrator who is not the owner of an administrator-hidden project.",
|
|
auth="admin",
|
|
interactive=True,
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
)
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-admin-index",
|
|
method="GET",
|
|
path="/admin/containers",
|
|
title="Admin containers list",
|
|
summary="The admin Containers section: every instance across all projects, each linking to its detail page. Instances attached to another administrator's hidden project are excluded, and per-instance actions return 404 for a non-owner administrator.",
|
|
auth="admin",
|
|
interactive=True,
|
|
),
|
|
endpoint(
|
|
id="containers-admin-data",
|
|
method="GET",
|
|
path="/admin/containers/data",
|
|
title="Admin containers list data",
|
|
summary="JSON of every instance across all projects (decorated with project title/slug) for polling.",
|
|
auth="admin",
|
|
sample_response={
|
|
"instances": [
|
|
{
|
|
"uid": "INSTANCE_UID",
|
|
"name": "staging",
|
|
"status": "running",
|
|
"project_slug": "PROJECT_SLUG",
|
|
"project_title": "My Project",
|
|
"ingress_slug": "my-service",
|
|
"restart_policy": "always",
|
|
}
|
|
]
|
|
},
|
|
),
|
|
endpoint(
|
|
id="containers-admin-instance",
|
|
method="GET",
|
|
path="/admin/containers/{uid}",
|
|
title="Instance detail page",
|
|
summary="The dedicated detail page for one instance (lifecycle, logs, metrics, terminal, schedules, ingress, sync).",
|
|
auth="admin",
|
|
interactive=True,
|
|
params=[
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
)
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-admin-edit-page",
|
|
method="GET",
|
|
path="/admin/containers/{uid}/edit",
|
|
title="Edit instance page",
|
|
summary="The edit page for one instance (run-as user, boot language/script/command, restart policy, start-on-boot, limits).",
|
|
auth="admin",
|
|
interactive=True,
|
|
params=[
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
)
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-create-instance",
|
|
method="POST",
|
|
path="/projects/{project_slug}/containers/instances",
|
|
title="Create an instance",
|
|
summary="Create and (by default) start an instance; it runs the shared ppy image with the project workspace mounted at /app.",
|
|
auth="admin",
|
|
encoding="form",
|
|
destructive=True,
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
),
|
|
field("name", "form", "string", True, "staging", "Instance name."),
|
|
field(
|
|
"boot_command",
|
|
"form",
|
|
"string",
|
|
False,
|
|
"python app.py",
|
|
"Optional boot command.",
|
|
),
|
|
field("run_as_uid", "form", "string", False, "USER_UID", "DevPlace user uid whose identity and API key are injected (PRAVDA_API_KEY, PRAVDA_USER_UID). Does NOT change the container OS user (always pravda, uid 1000)."),
|
|
field("boot_language", "form", "enum", False, "none", "Boot source language.", ["none", "python", "bash"]),
|
|
field("boot_script", "form", "textarea", False, "print('hi')", "Boot source code run on launch (takes precedence over boot_command)."),
|
|
field(
|
|
"env",
|
|
"form",
|
|
"textarea",
|
|
False,
|
|
"KEY=VALUE",
|
|
"Env vars, one KEY=VALUE per line.",
|
|
),
|
|
field(
|
|
"ports",
|
|
"form",
|
|
"string",
|
|
False,
|
|
"80",
|
|
"Port maps. Bare container port auto-assigns a unique host port above 20000; host:container pins one.",
|
|
),
|
|
field("cpu_limit", "form", "string", False, "1.5", "CPU limit."),
|
|
field(
|
|
"mem_limit", "form", "string", False, "512m", "Memory limit."
|
|
),
|
|
field(
|
|
"restart_policy",
|
|
"form",
|
|
"enum",
|
|
False,
|
|
"never",
|
|
"Restart policy.",
|
|
["never", "always", "on-failure", "unless-stopped"],
|
|
),
|
|
field("start_on_boot", "form", "boolean", False, "false", "Force running whenever the container service starts."),
|
|
field(
|
|
"ingress_slug",
|
|
"form",
|
|
"string",
|
|
False,
|
|
"my-service",
|
|
"Publish at /p/<slug> (optional).",
|
|
),
|
|
field(
|
|
"ingress_port",
|
|
"form",
|
|
"integer",
|
|
False,
|
|
"8899",
|
|
"Container port to publish (must be a mapped port).",
|
|
),
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-ingress",
|
|
method="GET",
|
|
path="/p/{slug}",
|
|
title="Container ingress proxy",
|
|
summary="Public reverse proxy (HTTP and WebSocket) to a running instance published via ingress_slug. The /p/<slug> prefix is stripped before forwarding.",
|
|
auth="public",
|
|
interactive=True,
|
|
params=[
|
|
field(
|
|
"slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"my-service",
|
|
"The instance's ingress_slug.",
|
|
)
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-instance-action",
|
|
method="POST",
|
|
path="/projects/{project_slug}/containers/instances/{uid}/{action}",
|
|
title="Instance lifecycle",
|
|
summary="start, stop, restart, pause, or resume an instance (flips desired state).",
|
|
auth="admin",
|
|
destructive=True,
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
),
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
),
|
|
field(
|
|
"action",
|
|
"path",
|
|
"enum",
|
|
True,
|
|
"start",
|
|
"Lifecycle action.",
|
|
["start", "stop", "restart", "pause", "resume"],
|
|
),
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-instance-logs",
|
|
method="GET",
|
|
path="/projects/{project_slug}/containers/instances/{uid}/logs",
|
|
title="Instance logs",
|
|
summary="Recent docker logs of a running instance.",
|
|
auth="admin",
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
),
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
),
|
|
field("tail", "query", "integer", False, "200", "Number of lines."),
|
|
],
|
|
sample_response={"logs": "..."},
|
|
),
|
|
endpoint(
|
|
id="containers-instance-sync",
|
|
method="POST",
|
|
path="/projects/{project_slug}/containers/instances/{uid}/sync",
|
|
title="Sync workspace",
|
|
summary="Run a one-shot bidirectional newer-wins sync between the project files and the container workspace.",
|
|
auth="admin",
|
|
destructive=True,
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
),
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
),
|
|
],
|
|
sample_response={"exported": 3, "imported": 1},
|
|
),
|
|
endpoint(
|
|
id="containers-instance-delete",
|
|
method="POST",
|
|
path="/projects/{project_slug}/containers/instances/{uid}/delete",
|
|
title="Delete instance",
|
|
summary="Remove a container instance and mark its container for removal.",
|
|
auth="admin",
|
|
destructive=True,
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
),
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
),
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-instance-exec",
|
|
method="POST",
|
|
path="/projects/{project_slug}/containers/instances/{uid}/exec",
|
|
title="Exec a command",
|
|
summary="Run a one-shot command inside a running instance and return its output.",
|
|
auth="admin",
|
|
encoding="form",
|
|
destructive=True,
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
),
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
),
|
|
field(
|
|
"command",
|
|
"form",
|
|
"string",
|
|
True,
|
|
"ls -la /app",
|
|
"Shell command to run (via /bin/sh -c).",
|
|
),
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-instance-data",
|
|
method="GET",
|
|
path="/projects/{project_slug}/containers/instances/{uid}",
|
|
title="Instance detail data",
|
|
summary="Return the full instance row plus runtime info as JSON.",
|
|
auth="admin",
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
),
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
),
|
|
],
|
|
sample_response={"uid": "INSTANCE_UID", "name": "staging", "status": "running"},
|
|
),
|
|
endpoint(
|
|
id="containers-instance-metrics",
|
|
method="GET",
|
|
path="/projects/{project_slug}/containers/instances/{uid}/metrics",
|
|
title="Instance metrics",
|
|
summary="Return recent metrics ring-buffer and aggregated stats for a running instance.",
|
|
auth="admin",
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
),
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
),
|
|
],
|
|
sample_response={"metrics": [], "stats": {}},
|
|
),
|
|
endpoint(
|
|
id="containers-instance-schedules",
|
|
method="POST",
|
|
path="/projects/{project_slug}/containers/instances/{uid}/schedules",
|
|
title="Create a schedule",
|
|
summary="Attach a cron, one-time, interval, or delay schedule to an instance.",
|
|
auth="admin",
|
|
encoding="form",
|
|
destructive=True,
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
),
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
),
|
|
field(
|
|
"action",
|
|
"form",
|
|
"string",
|
|
True,
|
|
"start",
|
|
"Lifecycle action to run on schedule (start, stop, restart).",
|
|
),
|
|
field(
|
|
"kind",
|
|
"form",
|
|
"string",
|
|
True,
|
|
"cron",
|
|
"Schedule kind: cron, once, interval, or delay.",
|
|
),
|
|
field(
|
|
"cron",
|
|
"form",
|
|
"string",
|
|
False,
|
|
"0 * * * *",
|
|
"Cron expression (when kind is cron).",
|
|
),
|
|
field(
|
|
"run_at",
|
|
"form",
|
|
"string",
|
|
False,
|
|
"2026-01-01T00:00:00",
|
|
"ISO timestamp for a one-time run (when kind is once).",
|
|
),
|
|
field(
|
|
"delay_seconds",
|
|
"form",
|
|
"integer",
|
|
False,
|
|
"60",
|
|
"Seconds to wait before a single run (when kind is delay).",
|
|
),
|
|
field(
|
|
"every_seconds",
|
|
"form",
|
|
"integer",
|
|
False,
|
|
"300",
|
|
"Interval in seconds between runs (when kind is interval).",
|
|
),
|
|
field(
|
|
"max_runs",
|
|
"form",
|
|
"integer",
|
|
False,
|
|
"10",
|
|
"Optional cap on the number of runs.",
|
|
),
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-instance-schedule-delete",
|
|
method="POST",
|
|
path="/projects/{project_slug}/containers/instances/{uid}/schedules/{sid}/delete",
|
|
title="Delete a schedule",
|
|
summary="Remove a schedule from an instance.",
|
|
auth="admin",
|
|
destructive=True,
|
|
params=[
|
|
field(
|
|
"project_slug",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"PROJECT_SLUG",
|
|
"Project slug or uid.",
|
|
),
|
|
field(
|
|
"uid", "path", "string", True, "INSTANCE_UID", "Instance uid."
|
|
),
|
|
field(
|
|
"sid", "path", "string", True, "SCHEDULE_UID", "Schedule uid."
|
|
),
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-admin-create",
|
|
method="POST",
|
|
path="/admin/containers/create",
|
|
title="Admin create instance",
|
|
summary="Create an instance from the admin Containers page: project search-select, run-as user, boot language/script, restart policy, start-on-boot, plus the usual options.",
|
|
auth="admin",
|
|
encoding="form",
|
|
destructive=True,
|
|
params=[
|
|
field("project_slug", "form", "string", True, "PROJECT_SLUG", "Project that becomes the /app root."),
|
|
field("name", "form", "string", True, "staging", "Instance name."),
|
|
field("run_as_uid", "form", "string", False, "USER_UID", "DevPlace user uid whose identity and API key are injected (PRAVDA_API_KEY, PRAVDA_USER_UID). Does NOT change the container OS user (always pravda, uid 1000)."),
|
|
field("boot_language", "form", "enum", False, "none", "Boot source language.", ["none", "python", "bash"]),
|
|
field("boot_script", "form", "textarea", False, "print('hi')", "Boot source code run on launch (takes precedence over boot_command)."),
|
|
field("boot_command", "form", "string", False, "python app.py", "Fallback boot command when no boot_script is set."),
|
|
field("restart_policy", "form", "enum", False, "never", "Restart policy.", ["never", "always", "on-failure", "unless-stopped"]),
|
|
field("start_on_boot", "form", "boolean", False, "false", "Force running whenever the container service starts."),
|
|
field("env", "form", "textarea", False, "KEY=VALUE", "Env vars, one KEY=VALUE per line."),
|
|
field("ports", "form", "string", False, "80", "Port maps; bare container port auto-assigns a host port above 20000."),
|
|
field("cpu_limit", "form", "string", False, "1.5", "CPU limit."),
|
|
field("mem_limit", "form", "string", False, "512m", "Memory limit."),
|
|
field("ingress_slug", "form", "string", False, "my-service", "Publish at /p/<slug> (optional)."),
|
|
field("ingress_port", "form", "integer", False, "8899", "Container port to publish."),
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-admin-edit",
|
|
method="POST",
|
|
path="/admin/containers/{uid}/edit",
|
|
title="Admin edit instance",
|
|
summary="Update an instance's run-as user, boot language/script/command, restart policy, start-on-boot flag, and resource limits.",
|
|
auth="admin",
|
|
encoding="form",
|
|
destructive=True,
|
|
params=[
|
|
field("uid", "path", "string", True, "INSTANCE_UID", "Instance uid."),
|
|
field("run_as_uid", "form", "string", False, "USER_UID", "Run-as user uid (identity + API key only)."),
|
|
field("boot_language", "form", "enum", False, "none", "Boot source language.", ["none", "python", "bash"]),
|
|
field("boot_script", "form", "textarea", False, "print('hi')", "Boot source code."),
|
|
field("boot_command", "form", "string", False, "python app.py", "Fallback boot command."),
|
|
field("restart_policy", "form", "enum", False, "never", "Restart policy.", ["never", "always", "on-failure", "unless-stopped"]),
|
|
field("start_on_boot", "form", "boolean", False, "false", "Force running on container-service boot."),
|
|
field("cpu_limit", "form", "string", False, "1.5", "CPU limit."),
|
|
field("mem_limit", "form", "string", False, "512m", "Memory limit."),
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-admin-action",
|
|
method="POST",
|
|
path="/admin/containers/{uid}/{action}",
|
|
title="Admin instance lifecycle",
|
|
summary="start, stop, restart, pause, or resume an instance from the admin Containers page (flips desired state).",
|
|
auth="admin",
|
|
destructive=True,
|
|
params=[
|
|
field("uid", "path", "string", True, "INSTANCE_UID", "Instance uid."),
|
|
field("action", "path", "enum", True, "start", "Lifecycle action.", ["start", "stop", "restart", "pause", "resume"]),
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-admin-sync",
|
|
method="POST",
|
|
path="/admin/containers/{uid}/sync",
|
|
title="Admin bidirectional sync",
|
|
summary="Run a one-shot bidirectional newer-wins sync between the project files and the container workspace.",
|
|
auth="admin",
|
|
destructive=True,
|
|
params=[
|
|
field("uid", "path", "string", True, "INSTANCE_UID", "Instance uid."),
|
|
],
|
|
sample_response={"exported": 3, "imported": 1},
|
|
),
|
|
endpoint(
|
|
id="containers-admin-delete",
|
|
method="POST",
|
|
path="/admin/containers/{uid}/delete",
|
|
title="Admin delete instance",
|
|
summary="Soft-delete an instance and mark its container for removal.",
|
|
auth="admin",
|
|
destructive=True,
|
|
params=[
|
|
field("uid", "path", "string", True, "INSTANCE_UID", "Instance uid."),
|
|
],
|
|
),
|
|
endpoint(
|
|
id="containers-admin-project-search",
|
|
method="GET",
|
|
path="/admin/containers/projects/search",
|
|
title="Admin project search",
|
|
summary="Search projects by title for the admin create form (returns uid, slug, title).",
|
|
auth="admin",
|
|
params=[
|
|
field("q", "query", "string", False, "api", "Title fragment."),
|
|
],
|
|
sample_response={"results": [{"uid": "PROJECT_UID", "slug": "PROJECT_SLUG", "title": "My Project"}]},
|
|
),
|
|
endpoint(
|
|
id="containers-admin-user-search",
|
|
method="GET",
|
|
path="/admin/containers/users/search",
|
|
title="Admin run-as user search",
|
|
summary="Search users by username for the run-as-user select (returns uid, username).",
|
|
auth="admin",
|
|
params=[
|
|
field("q", "query", "string", False, "alice", "Username fragment."),
|
|
],
|
|
sample_response={"results": [{"uid": "USER_UID", "username": "alice"}]},
|
|
),
|
|
],
|
|
}
|