Files
tgit/tests/runner/fake_docker.py
T
ElebobaandClaude Opus 5.5 c443190bf4
CI / ruff · mypy · pytest (pull_request) Successful in 4m3s
feat(runner): tgit-runner — сервисы в песочнице на хосте, деплой после push (#13)
- infra/runner/tgit-runner: deploy/restart/remove/list по SSH forced command;
  аргументы не исполняются и не глобятся; архив — только файлы и папки,
  без «..», ≤ 20 МБ, есть main.py; health_url — только localhost
- песочница: uid 10001, read-only, tmpfs noexec, cap-drop ALL,
  no-new-privileges, 128 МБ без swap, 0.25 CPU, 64 PID, --network none
  (сеть — только по egress-allow админа), лимит 10 сервисов, 2 образа
- новая версия стартует рядом (-next) и заменяет старую, только став
  healthy; иначе удаляется, старая работает, exit 3
- бот: после push git archive изменённых services/<name>/ → runner;
  автору — «🚀 работает» или «⛔ не развёрнут» с логом, команде — алерт ⛔;
  удалённая папка → remove; «♻️ Перезапустить» в «Статусе» для админа (S-5)
- examples/escape_attempts: все попытки выбраться — BLOCKED на живом Docker
- install.sh, authorized_keys.example; CD бота обновляет runner на хосте

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:36:29 +03:00

84 lines
2.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""A fake `docker` CLI for tgit-runner tests: records every call, keeps containers in a JSON file.
Behaviour knobs (env): FAKE_HEALTH (healthy|unhealthy|none), FAKE_STATUS (running|exited), FAKE_RESTARTS,
FAKE_BUILD_FAIL=1. State: $FAKE_DOCKER_STATE (json), calls: $FAKE_DOCKER_LOG (one json array per line).
"""
import json
import os
import sys
state_path = os.environ["FAKE_DOCKER_STATE"]
args = sys.argv[1:]
with open(os.environ["FAKE_DOCKER_LOG"], "a") as log:
log.write(json.dumps(args) + "\n")
try:
with open(state_path) as f:
state = json.load(f)
except FileNotFoundError:
state = {"containers": {}, "images": []}
def save() -> None:
with open(state_path, "w") as f:
json.dump(state, f)
def done(code: int = 0, out: str = "") -> None:
if out:
print(out)
save()
sys.exit(code)
cmd = args[0] if args else ""
if cmd == "container" and args[1] == "inspect":
done(0 if args[2] in state["containers"] else 1)
if cmd == "inspect":
fmt, name = args[2], args[3]
if name not in state["containers"]:
done(1)
if "Health" in fmt:
done(0, os.environ.get("FAKE_HEALTH", "healthy"))
if "RestartCount" in fmt:
done(0, os.environ.get("FAKE_RESTARTS", "0"))
done(0, os.environ.get("FAKE_STATUS", "running"))
if cmd == "ps":
done(0, "\n".join(n for n in state["containers"]))
if cmd == "build":
if os.environ.get("FAKE_BUILD_FAIL"):
done(1, "ERROR: could not install requirements")
tag = args[args.index("-t") + 1]
ctx = args[-1]
with open(os.path.join(ctx, "Dockerfile")) as f:
state.setdefault("dockerfiles", {})[tag] = f.read()
state["images"].append(tag)
done(0, "sha256:feed")
if cmd == "run":
name = args[args.index("--name") + 1]
state["containers"][name] = {"image": args[-1], "args": args}
done(0, "cid")
if cmd == "rm":
for name in args[2:]:
state["containers"].pop(name, None)
done(0)
if cmd == "rename":
state["containers"][args[2]] = state["containers"].pop(args[1])
done(0)
if cmd == "restart":
done(0)
if cmd == "logs":
done(0, "Traceback: boom")
if cmd == "image" and args[1] == "ls":
repo = args[2]
tags = [t.split(":", 1)[1] for t in reversed(state["images"]) if t.split(":", 1)[0] == repo]
full = "{{.Repository}}" in args[-1]
done(0, "\n".join(f"{repo}:{t}" if full else t for t in tags))
if cmd == "image" and args[1] == "rm":
state["images"] = [t for t in state["images"] if t != args[2]]
done(0)
if cmd == "network":
done(0)
done(2, f"fake docker: unsupported {args}")