api-server: Separate /logs endpoint

This commit is contained in:
Kacper Donat 2022-11-15 18:56:49 +01:00
parent fc94212d97
commit eca2cdddb1

View File

@ -1,53 +1,110 @@
import asyncio import asyncio
from os import system import uuid
from time import sleep from wsgiref.util import request_uri
from typing import Any, Dict
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import ansible_runner import ansible_runner
import os
from typing import Any, Dict
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
from ansible_runner import Runner
from dataclasses import dataclass
class Link(BaseModel):
href: str
class DeployArgs(BaseModel): class DeployArgs(BaseModel):
extra_vars: Dict[str, Any] service: str
inventory: str | None = None
vars: Dict[str, Any] = {}
class DeploymentLinks(BaseModel):
logs: Link
self: Link
@dataclass
class Deployment:
id: str
runner: Runner | None = None
class DeploymentDTO(BaseModel):
id: str
status: str
links: DeploymentLinks = Field(..., alias="_links")
def from_deployment(deployment: Deployment, request: Request):
return DeploymentDTO(
id=deployment.id,
status=deployment.runner.status,
_links={
"self": {"href": request.url_for("deployment", id=deployment.id)},
"logs": {"href": request.url_for("deployment_logs", id=deployment.id)},
},
)
deployments: Dict[str, Deployment] = {}
app = FastAPI() app = FastAPI()
@app.post("/deploy/{service}")
async def deploy(service: str, args: DeployArgs):
finished = False
lines = []
def finish_callback(_): @app.get("/deployment/{id}/logs")
nonlocal finished async def deployment_logs(id: str):
finished = True if id not in deployments:
raise HTTPException(status_code=404, detail="Deployment was not found")
def event_callback(data: Dict): runner = deployments.get(id).runner
if 'stdout' in data:
lines.append(data['stdout']) async def stream():
stdout = runner.stdout
while True:
while line := stdout.readline():
if line == "":
break
yield line
if runner.status != "running":
break
async def logs():
nonlocal lines
while not finished:
for line in lines:
yield line.rstrip() + "\n"
lines = []
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
ansible_runner.run_async( return StreamingResponse(stream(), media_type="text/plain")
playbook='deploy.yaml',
extravars={
'services': [service], @app.get("/deployment")
**args.extra_vars async def deployment_list(request: Request):
}, return [
private_data_dir='/home/api-server', DeploymentDTO.from_deployment(deployment, request)
project_dir='/var/project', for deployment in deployments.values()
inventory='inventory/m2.ini', ]
event_handler=event_callback,
finished_callback=finish_callback,
settings={ @app.get("/deployment/{id}")
'suppress_ansible_output': True async def deployment(id: str, request: Request):
} return DeploymentDTO.from_deployment(deployments[id], request)
@app.post("/deployment")
async def deploy(args: DeployArgs, request: Request):
ident = str(uuid.uuid4())
_, runner = ansible_runner.run_async(
playbook="deploy.yaml",
ident=ident,
extravars={"services": [args.service], **args.vars},
private_data_dir="/home/api-server",
project_dir=os.environ.get("API_PROJECT_DIR", "/var/project"),
inventory=args.inventory or os.environ.get("API_INVENTORY"),
settings={"suppress_ansible_output": True},
) )
return StreamingResponse(logs(), media_type='text/plain') deployment = Deployment(ident, runner)
deployments[ident] = deployment
return DeploymentDTO.from_deployment(deployment, request)