diff options
| author | Panu Oksiala <panu@oksiala.fi> | 2024-01-22 00:24:37 +0200 |
|---|---|---|
| committer | Panu Oksiala <panu@oksiala.fi> | 2024-01-22 00:24:37 +0200 |
| commit | 73c1115e6fba24b86eca1e5cad5f5086d6bd2ba6 (patch) | |
| tree | daedbc44f41c91a13cb7fc26edd325ab14996217 /desmo_api/api.py | |
Initial commit
Diffstat (limited to 'desmo_api/api.py')
| -rw-r--r-- | desmo_api/api.py | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/desmo_api/api.py b/desmo_api/api.py new file mode 100644 index 0000000..750904d --- /dev/null +++ b/desmo_api/api.py @@ -0,0 +1,112 @@ +from fastapi import FastAPI +from typing import Dict +from .fsm import JailStateMachine +import logging +import sys +import asyncio +import statemachine.exceptions +from contextlib import asynccontextmanager +from .hcloud_dns import HCloudDNS +import os +from . import db, models +import random +import secrets + +logging.basicConfig(stream=sys.stderr, level=logging.INFO) +logger = logging.getLogger("api") + +STATE_MACHINES: Dict[str, JailStateMachine] = {} + +DNS_CLIENT = HCloudDNS(os.environ["HCLOUD_DNS_KEY"]) +database = db.DB(os.environ["DATABASE_DSN"]) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info("Running migrations") + await database.migrate() + logger.info("Loading servers from database") + jails = await database.get_jails() + logger.info("Loaded %s jails from database", len(jails)) + logger.info(jails) + for jail in jails: + logger.info("Loading server %s with state %s", jail.name, jail.state) + _fsm = JailStateMachine(DNS_CLIENT, database, jail.name) + _fsm.current_state_value = jail.state + _fsm.start_on_enter_task() + STATE_MACHINES[jail.name] = _fsm + yield + logger.info("Closing asyncio clients") + await DNS_CLIENT.close() + await database.close() + logger.info("Closed asyncio clients") + + +app = FastAPI(lifespan=lifespan) + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.post( + "/jails", + status_code=201, +) +async def create_jail(req: models.CreateJailRequest) -> models.FullJailInfo: + first_part = secrets.token_hex(2) + second_part = secrets.token_hex(2) + name = f"{req.name}-{first_part}-{second_part}" + ip = os.environ["NETWORK_PREFIX"] + "::" + first_part + ":" + second_part + hosts = os.environ["RUNNER_HOSTS"].split(",") + host = random.choice(hosts) + state = "uninitialized" + + await database.insert_jail(name, host, ip, state) + for package in req.packages: + await database.insert_jail_package(name, package) + + for i, command in enumerate(req.commands): + await database.insert_jail_command(name, command, i) + + STATE_MACHINES[name] = JailStateMachine(DNS_CLIENT, database, name) + STATE_MACHINES[name].initialize() + await asyncio.sleep(1) + return models.FullJailInfo( + name=name, + state=state, + ip=ip, + host=host, + packages=req.packages, + commands=req.commands, + ) + + +@app.get("/jails/{name}") +async def get_server(name: str) -> models.FullJailInfo | Dict[str, str]: + if name not in STATE_MACHINES: + return {"error": "server does not exist"} + jail = await database.get_jail(name) + packages = await database.get_jail_packages(name) + commands = await database.get_jail_commands(name) + return models.FullJailInfo( + name=jail.name, + state=jail.state, + ip=jail.ip, + host=jail.host, + packages=packages, + commands=commands, + ) + + +@app.delete("/jails/{name}") +async def delete_server(name: str) -> Dict[str, str]: + if name not in STATE_MACHINES: + return {"error": "jail does not exist"} + try: + STATE_MACHINES[name].remove_jail() + except statemachine.exceptions.TransitionNotAllowed: + return {"error": "jail is not ready"} + await asyncio.sleep(1) + return {"status": "ok"} |
