worked on pillar endpoints
This commit is contained in:
parent
ace0062f37
commit
37fa6bcbb3
@ -1,7 +1,9 @@
|
|||||||
from typing import Any
|
import json
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from pillar_tool.db.models.pillar_data import *
|
from pillar_tool.db.models.pillar_data import *
|
||||||
|
|
||||||
|
from uuid import UUID
|
||||||
from sqlalchemy import select, insert, union
|
from sqlalchemy import select, insert, union
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
@ -9,57 +11,32 @@ from sqlalchemy.orm import Session
|
|||||||
def get_pillar_name_sequence(name: str) -> list[str]:
|
def get_pillar_name_sequence(name: str) -> list[str]:
|
||||||
return name.split(':')
|
return name.split(':')
|
||||||
|
|
||||||
|
def decode_pillar_value(pillar: Pillar) -> str | int | float | bool | list | dict:
|
||||||
def generate_host_hierarchy(db: Session, labels: list[str]) -> list[Host]:
|
match pillar.type:
|
||||||
path_consumed = []
|
case 'string': return pillar.value
|
||||||
out = []
|
case 'integer': return int(pillar.value)
|
||||||
last_parent_id = None
|
case 'float': return float(pillar.value)
|
||||||
for label in labels:
|
case 'boolean': return bool(pillar.value)
|
||||||
path_consumed += label
|
case 'array': return json.loads(pillar.value)
|
||||||
stmt = select(Host).where(Host.name == label and Host.parent_id == last_parent_id)
|
case 'dict': return json.loads(pillar.value)
|
||||||
result = list(db.execute(stmt).fetchall())
|
raise RuntimeError(f"Failed to decode pillar value: Invalid type '{pillar.type}'")
|
||||||
if not result:
|
|
||||||
raise RuntimeError(f"No such host(-group): '{':'.join(path_consumed)}'")
|
|
||||||
# NOTE: this is an assertion because the schema should enforce this
|
|
||||||
assert len(result) == 1
|
|
||||||
instance = Host(result[0])
|
|
||||||
last_parent_id = instance.id
|
|
||||||
out.append(instance)
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def get_values_for_host(db: Session, host: str) -> dict:
|
def get_pillar_for_target(db: Session, target: UUID) -> dict:
|
||||||
labels = get_pillar_name_sequence(host)
|
pillar_stmt = select(Pillar).where(Pillar.host_id == target)
|
||||||
hierarchy = generate_host_hierarchy(db, labels)
|
result = db.execute(pillar_stmt).fetchall()
|
||||||
|
|
||||||
# TODO: generate host hierarchy
|
out = {}
|
||||||
# TODO: find all values assigned o this host hierarchy and sort by depth
|
for row in result:
|
||||||
# TODO: build the pillar structure
|
row: Pillar = row[0]
|
||||||
|
name = row.pillar_name
|
||||||
return {}
|
value = decode_pillar_value(row)
|
||||||
|
labels = get_pillar_name_sequence(name)
|
||||||
|
current = out
|
||||||
def create_pillar_host(db: Session, host_id: UUID, name: str, value: Any) -> None:
|
l = len(labels)
|
||||||
# TODO: generate host hierarchy
|
for i, label in enumerate(labels):
|
||||||
# get the involved host or hostgroup
|
if label not in current:
|
||||||
res = db.execute(select(Host).where(Host.id == host_id)).fetchone()
|
current[label] = {} if i < l-1 else value
|
||||||
if res is None:
|
|
||||||
# TODO: handle this error with a custom Exception
|
|
||||||
raise RuntimeError(f"No Host or Hostgroup with id {host_id} exists!")
|
|
||||||
host = res[0][0]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: generate pillar path from name
|
|
||||||
|
|
||||||
# TODO: find if pillar already exists
|
|
||||||
# TODO: create new pillar if it doesn't exist
|
|
||||||
# TODO: assign value to new or existing pillar
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_pillar_host_group(db: Session, host_group: UUID, name: str, value: Any) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
print(json.dumps(out, indent=4))
|
||||||
|
pass
|
||||||
@ -27,6 +27,7 @@ from pillar_tool.routers.host import router as host_router
|
|||||||
from pillar_tool.routers.hostgroup import router as hostgroup_router
|
from pillar_tool.routers.hostgroup import router as hostgroup_router
|
||||||
from pillar_tool.routers.environment import router as environment_router
|
from pillar_tool.routers.environment import router as environment_router
|
||||||
from pillar_tool.routers.state import router as state_router
|
from pillar_tool.routers.state import router as state_router
|
||||||
|
from pillar_tool.routers.pillar import router as pillar_router
|
||||||
|
|
||||||
# run any pending migrations
|
# run any pending migrations
|
||||||
run_db_migrations()
|
run_db_migrations()
|
||||||
@ -69,11 +70,12 @@ app.add_middleware(BaseHTTPMiddleware, dispatch=db_connection_middleware)
|
|||||||
app.add_middleware(BaseHTTPMiddleware, dispatch=request_logging_middleware)
|
app.add_middleware(BaseHTTPMiddleware, dispatch=request_logging_middleware)
|
||||||
app.exception_handler(Exception)(on_general_error)
|
app.exception_handler(Exception)(on_general_error)
|
||||||
|
|
||||||
# Setup the api router
|
# Set up the api router
|
||||||
app.include_router(host_router)
|
app.include_router(host_router)
|
||||||
app.include_router(hostgroup_router)
|
app.include_router(hostgroup_router)
|
||||||
app.include_router(environment_router)
|
app.include_router(environment_router)
|
||||||
app.include_router(state_router)
|
app.include_router(state_router)
|
||||||
|
app.include_router(pillar_router)
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
|
|||||||
@ -15,7 +15,7 @@ def hostgroup():
|
|||||||
def hostgroup_list():
|
def hostgroup_list():
|
||||||
click.echo("Listing known hostgroups...")
|
click.echo("Listing known hostgroups...")
|
||||||
try:
|
try:
|
||||||
response = requests.get(f'{base_url}/hostgroup', headers=auth_header())
|
response = requests.get(f'{base_url()}/hostgroup', headers=auth_header())
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
click.echo("Hostgroups:")
|
click.echo("Hostgroups:")
|
||||||
@ -36,7 +36,7 @@ def hostgroup_show(path: str):
|
|||||||
data = HostgroupParams(
|
data = HostgroupParams(
|
||||||
path=path
|
path=path
|
||||||
)
|
)
|
||||||
response = requests.get(f'{base_url}/hostgroup/{name}', headers=auth_header(), params=data.model_dump())
|
response = requests.get(f'{base_url()}/hostgroup/{name}', headers=auth_header(), params=data.model_dump())
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
raise click.ClickException(f"Failed to show hostgroup:\n{e}")
|
raise click.ClickException(f"Failed to show hostgroup:\n{e}")
|
||||||
@ -53,7 +53,7 @@ def hostgroup_create(path: str):
|
|||||||
data = HostgroupParams(
|
data = HostgroupParams(
|
||||||
path=path
|
path=path
|
||||||
)
|
)
|
||||||
response = requests.post(f'{base_url}/hostgroup/{name}', headers=auth_header(), json=data.model_dump())
|
response = requests.post(f'{base_url()}/hostgroup/{name}', headers=auth_header(), json=data.model_dump())
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
raise click.ClickException(f"Failed to create hostgroup:\n{e}")
|
raise click.ClickException(f"Failed to create hostgroup:\n{e}")
|
||||||
@ -68,7 +68,7 @@ def hostgroup_delete(path: str):
|
|||||||
name = labels[-1]
|
name = labels[-1]
|
||||||
prefix = "/".join(labels[:-1]) if len(labels) > 1 else None
|
prefix = "/".join(labels[:-1]) if len(labels) > 1 else None
|
||||||
query_params = f"?path={prefix}" if prefix is not None else ''
|
query_params = f"?path={prefix}" if prefix is not None else ''
|
||||||
response = requests.delete(f'{base_url}/hostgroup/{name}{query_params}', headers=auth_header())
|
response = requests.delete(f'{base_url()}/hostgroup/{name}{query_params}', headers=auth_header())
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
raise click.ClickException(f"Failed to delete hostgroup:\n{e}")
|
raise click.ClickException(f"Failed to delete hostgroup:\n{e}")
|
||||||
|
|||||||
@ -6,4 +6,20 @@ from .cli_main import main, auth_header, base_url
|
|||||||
|
|
||||||
@main.group("pillar")
|
@main.group("pillar")
|
||||||
def pillar():
|
def pillar():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pillar.command("get")
|
||||||
|
@click.argument("fqdn")
|
||||||
|
def pillar_get(fqdn):
|
||||||
|
"""Get pillar data for a given FQDN."""
|
||||||
|
try:
|
||||||
|
response = requests.get(
|
||||||
|
f"{base_url()}/pillar/{fqdn}",
|
||||||
|
headers=auth_header(),
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
pillar_data = response.json()
|
||||||
|
click.echo(pillar_data)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
click.echo(f"Error: {e}")
|
||||||
@ -1,15 +1,17 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from fastapi.params import Depends
|
from sqlalchemy import select, insert, delete, bindparam
|
||||||
from sqlalchemy import select, insert, delete
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, Depends
|
||||||
from starlette.responses import JSONResponse
|
from starlette.responses import JSONResponse
|
||||||
|
|
||||||
|
from pillar_tool.db import Host
|
||||||
from pillar_tool.db.models.top_data import State, StateAssignment
|
from pillar_tool.db.models.top_data import State, StateAssignment
|
||||||
|
from pillar_tool.db.queries.pillar_queries import get_pillar_for_target
|
||||||
from pillar_tool.schemas import PillarParams, get_model_from_query
|
from pillar_tool.schemas import PillarParams, get_model_from_query
|
||||||
|
from pillar_tool.util.pillar_utilities import merge
|
||||||
from pillar_tool.util.validation import validate_state_name
|
from pillar_tool.util.validation import validate_state_name
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
@ -19,8 +21,8 @@ router = APIRouter(
|
|||||||
|
|
||||||
# Note: there is no list of all pillars, as this would not be helpful
|
# Note: there is no list of all pillars, as this would not be helpful
|
||||||
|
|
||||||
@router.get("/{name}")
|
@router.get("/{fqdn}")
|
||||||
def state_get(req: Request, name: str, params: Depends(get_model_from_query(PillarParams))):
|
def pillar_get(req: Request, fqdn: str):
|
||||||
# TODO: implement
|
# TODO: implement
|
||||||
# this function should:
|
# this function should:
|
||||||
# - get the affected host hierarchy
|
# - get the affected host hierarchy
|
||||||
@ -30,14 +32,38 @@ def state_get(req: Request, name: str, params: Depends(get_model_from_query(Pill
|
|||||||
# if any error happens, return non-200 status and an empty dictionary so that salt does not shit itself
|
# if any error happens, return non-200 status and an empty dictionary so that salt does not shit itself
|
||||||
db: Session = req.state.db
|
db: Session = req.state.db
|
||||||
|
|
||||||
|
# get the host hierarchy
|
||||||
|
host_stmt = select(Host).where(Host.name == fqdn and Host.is_hostgroup == False)
|
||||||
|
result = db.execute(host_stmt).fetchall()
|
||||||
|
if len(result) == 0:
|
||||||
|
return JSONResponse(status=404, content={})
|
||||||
|
# NOTE: should be enforced by the database
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
@router.post("/{name}")
|
host: Host = result[0][0]
|
||||||
def state_create(req: Request, name: str):
|
path: list[Host] = [host]
|
||||||
|
parent_stmt = select(Host).where(Host.id == bindparam('parent'))
|
||||||
|
while path[-1].parent_id is not None:
|
||||||
|
result = db.execute(parent_stmt, {'parent': path[-1].parent_id}).fetchall()
|
||||||
|
# NOTE: should be enforced by the database
|
||||||
|
assert len(result) == 1
|
||||||
|
tmp: Host = result[0][0]
|
||||||
|
path.append(tmp)
|
||||||
|
|
||||||
|
path.reverse()
|
||||||
|
out = merge(get_pillar_for_target(db, host.id) for host in path)
|
||||||
|
|
||||||
|
return JSONResponse(status_code=200, content={})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{fqdn}")
|
||||||
|
def pillar_create(req: Request, fqdn: str, params: PillarParams):
|
||||||
# TODO: implement
|
# TODO: implement
|
||||||
db = req.state.db
|
db = req.state.db
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{name}")
|
@router.delete("/{fqdn}")
|
||||||
def state_delete(req: Request, name: str):
|
def pillar_delete(req: Request, fqdn: str):
|
||||||
# TODO: implement
|
# TODO: implement
|
||||||
db = req.state.db
|
db = req.state.db
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user