Compare commits

..

No commits in common. "9216c95f10b4b3c11de196402e3e0d7c39d58917" and "bfb568b456b5a414f3c3527f2d23980eb3bb8abf" have entirely different histories.

6 changed files with 48 additions and 134 deletions

View File

@ -3,9 +3,8 @@ import base64
import click import click
import requests import requests
from urllib.parse import quote
from pillar_tool.schemas import HostCreateParams, HostgroupParams from pillar_tool.schemas import HostCreateParams
from pillar_tool.util import config, load_config, Config from pillar_tool.util import config, load_config, Config
from pillar_tool.util.validation import split_and_validate_path, validate_fqdn from pillar_tool.util.validation import split_and_validate_path, validate_fqdn
@ -121,51 +120,14 @@ def hostgroup_list():
raise click.ClickException(f"Failed to list hostgroups:\n{e}") raise click.ClickException(f"Failed to list hostgroups:\n{e}")
@hostgroup.command("show")
@click.argument("path")
def hostgroup_show(path: str):
click.echo(f"Showing hostgroup '{path}'...")
try:
labels = split_and_validate_path(path)
name = labels[-1]
path = '/'.join(labels[:-1]) if len(labels) > 1 else None
data = HostgroupParams(
path=path
)
response = requests.get(f'{base_url}/hostgroup/{name}', headers=auth_header, params=data.model_dump())
response.raise_for_status()
except requests.exceptions.HTTPError as e:
raise click.ClickException(f"Failed to show hostgroup:\n{e}")
@hostgroup.command("create") @hostgroup.command("create")
@click.argument("path") @click.argument("path")
def hostgroup_create(path: str): def hostgroup_create(path: str):
click.echo(f"Creating hostgroup '{path}'...") click.echo("TODO: implement")
try:
labels = split_and_validate_path(path)
path = "/".join(labels[:-1]) if len(labels) > 1 else ''
name = labels[-1]
data = HostgroupParams(
path=path
)
response = requests.post(f'{base_url}/hostgroup/{name}', headers=auth_header, json=data.model_dump())
response.raise_for_status()
except requests.exceptions.HTTPError as e:
raise click.ClickException(f"Failed to create hostgroup:\n{e}")
@hostgroup.command("delete") @hostgroup.command("delete")
@click.argument("path") @click.argument("path")
def hostgroup_delete(path: str): def hostgroup_delete(path: str):
click.echo(f"Deleting hostgroup {path}...") click.echo("TODO: implement")
try:
labels = split_and_validate_path(path)
name = labels[-1]
prefix = "/".join(labels[:-1]) if len(labels) > 1 else None
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.raise_for_status()
except requests.exceptions.HTTPError as e:
raise click.ClickException(f"Failed to delete hostgroup:\n{e}")

View File

@ -1,15 +1,14 @@
import uuid from http.client import HTTPResponse
from sqlalchemy import select, insert, bindparam
from sqlalchemy import select, insert, bindparam, 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, Query, Depends from fastapi import APIRouter
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from pillar_tool.db import Host from pillar_tool.db import Host
from pillar_tool.schemas import HostgroupParams, get_hostgroup_params_from_query, get_model_from_query from pillar_tool.schemas import HostgroupCreateParams
from pillar_tool.util.validation import split_and_validate_path from pillar_tool.util.validation import split_and_validate_path
router = APIRouter( router = APIRouter(
@ -34,20 +33,13 @@ def hostgroups_get(req: Request):
result = db.execute(select(Host).where(Host.is_hostgroup == True)).fetchall() result = db.execute(select(Host).where(Host.is_hostgroup == True)).fetchall()
hosts: list[Host] = list(map(lambda x: x[0], result)) hosts: list[Host] = list(map(lambda x: x[0], result))
all_hostgroups = { x.id: x for x in hosts } return JSONResponse(status_code=200, content=list(map(lambda x: x.name, hosts)))
all_hostgroup_names = []
for host in hosts:
ancestors = [host]
while ancestors[-1].parent_id is not None:
ancestors.append(all_hostgroups[ancestors[-1].parent_id])
all_hostgroup_names.append('/'.join(map(lambda x: x.name, reversed(ancestors))))
return JSONResponse(status_code=200, content=all_hostgroup_names)
@router.get("/{name}") @router.get("/{name}")
def hostgroup_get(req: Request, name: str, params: HostgroupParams = Depends(get_model_from_query(HostgroupParams))): def hostgroup_get(req: Request, name: str, path_input: str | None = None):
""" """
Retrieve a specific host group by name with additional details Retrieve a specific host group by name.
Fetches and returns details of the specified host group. Fetches and returns details of the specified host group.
Returns 404 if no such host group exists. Returns 404 if no such host group exists.
@ -55,7 +47,7 @@ def hostgroup_get(req: Request, name: str, params: HostgroupParams = Depends(get
Args: Args:
req (Request): The incoming request object. req (Request): The incoming request object.
name (str): The name of the host group to retrieve. name (str): The name of the host group to retrieve.
params (HostgroupCreateParams): the path of the group if desired path_input (str): the path of the group if desired
Returns: Returns:
JSONResponse: A JSON response with status code 200 and the host group details on success, JSONResponse: A JSON response with status code 200 and the host group details on success,
@ -66,20 +58,21 @@ def hostgroup_get(req: Request, name: str, params: HostgroupParams = Depends(get
# decode the path # decode the path
last = None last = None
ancestors = [] ancestors = []
path = split_and_validate_path(params.path) if params.path else [] if path_input is not None:
path = split_and_validate_path(path_input)
# get the path from the db # get the path from the db
path_stmt = select(Host).where(Host.name == bindparam('name') and Host.parent_id == bindparam('parent_id')) path_stmt = select(Host).where(Host.name == bindparam('name') and Host.parent_id == bindparam('parent_id'))
for label in path: for label in path:
result = db.execute(path_stmt, {'name': label, 'parent_id': last}).fetchall() result = db.execute(path_stmt, {'name': label, 'parent_id': last}).fetchall()
# error 404 if there is no matching item # error 404 if there is no matching item
if len(result) != 1: if len(result) != 1:
raise HTTPException(status_code=404, detail="No such hostgroup path exists") raise HTTPException(status_code=404, detail="No such hostgroup path exists")
tmp: Host = result[0][0] tmp: Host = result[0][0]
ancestors.append(tmp) ancestors.append(tmp)
last = tmp.id last = tmp.id
# get the host in question # get the host in question
stmt = select(Host).where(Host.name == name and Host.is_hostgroup == True and Host.parent_id == last) stmt = select(Host).where(Host.name == name and Host.is_hostgroup == True and Host.parent_id == last)
@ -90,6 +83,7 @@ def hostgroup_get(req: Request, name: str, params: HostgroupParams = Depends(get
# Note: this should be enforced by the database # Note: this should be enforced by the database
assert len(result) == 1 assert len(result) == 1
print("check 1")
hg: Host = result[0][0] hg: Host = result[0][0]
@ -100,7 +94,7 @@ def hostgroup_get(req: Request, name: str, params: HostgroupParams = Depends(get
@router.post("/{name}") @router.post("/{name}")
def hostgroup_create(req: Request, name: str, params: HostgroupParams): def hostgroup_create(req: Request, name: str, params: HostgroupCreateParams):
""" """
Create a new host group. Create a new host group.
@ -109,40 +103,37 @@ def hostgroup_create(req: Request, name: str, params: HostgroupParams):
Args: Args:
req (Request): The incoming request object. req (Request): The incoming request object.
name (str): The name of the host group (used as identifier). name (str): The name of the host group (used as identifier).
params (HostgroupCreateParams): Additional Parameters params (HostgroupCreateParams): The creation parameters (e.g., description, associated hosts).
Returns: Returns:
JSONResponse: A JSON response with status code 201 on success, JSONResponse: A JSON response with status code 201 on success,
or appropriate error codes (e.g., 409 if already exists). or appropriate error codes (e.g., 409 if already exists).
""" """
db = req.state.db pass
path = params.path
labels = split_and_validate_path(path) if path is not None else []
labels += [ name ]
stmt = select(Host).where(Host.name == bindparam('name') and Host.is_hostgroup == True and Host.parent_id == bindparam('last'))
last = None
for label in labels:
result = db.execute(stmt, {'name': label, 'last': last}).fetchall()
if len(result) == 1: @router.patch("/{name}")
# simply step down through the hierarchy def hostgroup_update(req: Request, name: str, params: HostgroupCreateParams):
host = result[0][0] """
last = host.id Update an existing host group by name.
elif len(result) == 0:
new_id = uuid.uuid4()
db.execute(insert(Host).values(id=new_id, name=label, is_hostgroup=True, parent_id=last))
last = new_id
else:
# this should not be possible
assert False
# TODO: return the newly created hostgroups Updates the specified host group with new parameters.
return JSONResponse(status_code=201, content={}) Returns 404 if no such host group exists.
Args:
req (Request): The incoming request object.
name (str): The current name of the host group to update.
params (HostgroupCreateParams): The updated parameters.
Returns:
JSONResponse: A JSON response with status code 200 on success,
or 404 if not found.
"""
pass
@router.delete("/{name}") @router.delete("/{name}")
def hostgroup_delete(req: Request, name: str, params: HostgroupParams = Depends(get_model_from_query(HostgroupParams))): def hostgroup_delete(req: Request, name: str, params: HostgroupCreateParams):
""" """
Delete a host group by name. Delete a host group by name.
@ -158,32 +149,4 @@ def hostgroup_delete(req: Request, name: str, params: HostgroupParams = Depends(
JSONResponse: A JSON response with status code 204 on successful deletion, JSONResponse: A JSON response with status code 204 on successful deletion,
or 404 if not found. or 404 if not found.
""" """
db = req.state.db pass
labels = split_and_validate_path(params.path) or []
labels.append(name)
last = None
stmt_step = select(Host).where(Host.name == bindparam('name') and Host.parent_id == bindparam('last') and Host.is_hostgroup == True)
for label in labels:
result = db.execute(stmt_step, {'name': label, 'last': last}).fetchall()
if len(result) == 0:
return JSONResponse(status_code=404, content={}) # TODO: truly define a error format
# this should be enforced by the database
assert len(result) == 1
host: Host = result[0][0]
last = host.id
children_stmt = select(Host).where(Host.parent_id == last)
children: list[Host] = list(map(lambda x: x[0], db.execute(children_stmt).fetchall()))
if len(children) != 0:
return JSONResponse(status_code=400, content={
'message': "Cannot delete a hostgroup that still has children",
'children': [ '/'.join(labels + [x.name]) for x in children ]
})
db.execute(delete(Host).where(Host.id == last))
return JSONResponse(status_code=204, content={})

View File

@ -1,7 +1,4 @@
from typing import Type, Callable
from pydantic import BaseModel from pydantic import BaseModel
from starlette.requests import Request
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
@ -43,14 +40,6 @@ class HostCreateParams(BaseModel):
parent: str | None parent: str | None
# Hostgroup operations # Hostgroup operations
class HostgroupParams(BaseModel): class HostgroupCreateParams(BaseModel):
path: str | None parent: str | None
def get_hostgroup_params_from_query(req: Request):
return HostgroupParams(**dict(req.query_params))
def get_model_from_query[T](model: T) -> Callable[[Request], T]:
def aux(req: Request) -> T:
return model.model_validate(dict(req.query_params or {}))
return aux