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
from os import system
from time import sleep
from typing import Any, Dict
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import uuid
from wsgiref.util import request_uri
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):
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.post("/deploy/{service}")
async def deploy(service: str, args: DeployArgs):
finished = False
lines = []
def finish_callback(_):
nonlocal finished
finished = True
@app.get("/deployment/{id}/logs")
async def deployment_logs(id: str):
if id not in deployments:
raise HTTPException(status_code=404, detail="Deployment was not found")
def event_callback(data: Dict):
if 'stdout' in data:
lines.append(data['stdout'])
runner = deployments.get(id).runner
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)
ansible_runner.run_async(
playbook='deploy.yaml',
extravars={
'services': [service],
**args.extra_vars
},
private_data_dir='/home/api-server',
project_dir='/var/project',
inventory='inventory/m2.ini',
event_handler=event_callback,
finished_callback=finish_callback,
settings={
'suppress_ansible_output': True
}
return StreamingResponse(stream(), media_type="text/plain")
@app.get("/deployment")
async def deployment_list(request: Request):
return [
DeploymentDTO.from_deployment(deployment, request)
for deployment in deployments.values()
]
@app.get("/deployment/{id}")
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)