# load config so everything else can work from pillar_tool.util import load_config, config from pillar_tool.util.validation import validate_and_split_path_and_domain_name load_config() from http.server import BaseHTTPRequestHandler from pillar_tool.db.base_model import as_dict from pillar_tool.middleware.logging import request_logging_middleware from pillar_tool.schemas import HostCreateParams from starlette.middleware.base import BaseHTTPMiddleware from pillar_tool.db.database import get_connection from pillar_tool.db.queries.auth_queries import create_user from pillar_tool.db.queries.host_queries import * from fastapi import FastAPI from starlette.middleware.authentication import AuthenticationMiddleware from starlette.requests import Request from fastapi.responses import HTMLResponse, PlainTextResponse from starlette.responses import JSONResponse from pillar_tool.middleware.basicauth_backend import BasicAuthBackend from pillar_tool.middleware.db_connection import db_connection_middleware from pillar_tool.db.database import run_db_migrations # run any pending migrations run_db_migrations() # get a database connection db = get_connection() # create default user if it does not exist # noinspection PyBroadException try: create_user(db, "admin", "admin") except: pass # commit and close the db db.commit() db.close() def on_auth_error(request: Request, exc: Exception): response = PlainTextResponse(str(exc), status_code=401) response.headers["WWW-Authenticate"] = "Basic" return response def on_db_error(request: Request, exc: Exception): response = PlainTextResponse(str(exc), status_code=500) return response def on_general_error(request: Request, exc: Exception): print("wtf?") response = PlainTextResponse(str(exc), status_code=500) return response app = FastAPI() app.add_middleware(AuthenticationMiddleware, backend=BasicAuthBackend(), on_error=on_auth_error) app.add_middleware(BaseHTTPMiddleware, dispatch=db_connection_middleware) app.add_middleware(BaseHTTPMiddleware, dispatch=request_logging_middleware) app.exception_handler(Exception)(on_general_error) @app.get("/") async def root(): return {"message": "Hello World"} @app.get("/health") async def health(): # TODO: improve health check return {"message": "Healthy"} @app.get("/pillar/{host}") async def pillar_get(req: Request, host: str): print(req.headers) #return JSONResponse(content=collect_pillar_data(host)) return JSONResponse({}) @app.post("/pillar/{host}") async def pillar_set(request: Request, host: str, value: str): return JSONResponse({ "captain.linvogel.internal": { "states": ["state1", "state2"], "test": { "pillar": "value" } } }) @app.get("/hosts") async def host_list(request: Request): all_hosts = list_all_hosts(request.state.db) return JSONResponse([x.name for x in all_hosts if x.parent_id is None]) @app.get("/hostgroups") async def hostgroup_list(request: Request): all_hosts = list_all_hosts(request.state.db) return JSONResponse([x.name for x in all_hosts if x.parent_id is not None]) @app.post("/host/{fqdn}") async def host_add(request: Request, fqdn: str, params: HostCreateParams): # Validate and split FQDN into hierarchical labels (e.g., "a/b/c" -> ["a", "b", "c"]) labels = validate_and_split_path_and_domain_name(fqdn) if labels is None: raise HTTPException(status_code=400, detail="Invalid Path provided") # walk through the labels and create the requested groups and host created = [] last_parent = None parent: str | None = params.parent # start with the optional parent parameter for label in labels: new_host = create_host(request.state.db, label, parent) last_parent = parent if parent is not None: # update path to parent for the next label parent += f"/{new_host.name}" else: # set first level otherwise parent = new_host.name created.append(new_host) # Prepare response with creation details output = { "message": "Host created", "host": created[-1], # return the final host in the hierarchy } # include the full path to the new host if it exists if last_parent is not None: output.update({ "path": last_parent }) return JSONResponse(output) @app.delete("/host/{fqdn}") async def host_delete(request: Request, fqdn: str): delete_host(request.state.db, fqdn) return JSONResponse({}) @app.get("/top/{fqdn}") async def host_top(request: Request, fqdn: str): # TODO: implement return JSONResponse({})