pillars can now be set
This commit is contained in:
parent
8ec26d53e4
commit
e9421790e1
@ -0,0 +1,34 @@
|
||||
"""renamed bad parameter
|
||||
|
||||
Revision ID: ec7c818f92b5
|
||||
Revises: 58c2a8e7c302
|
||||
Create Date: 2026-02-21 23:38:00.609470
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'ec7c818f92b5'
|
||||
down_revision: Union[str, Sequence[str], None] = '58c2a8e7c302'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('pillar_tool_pillar', sa.Column('parameter_type', sa.String(), nullable=False))
|
||||
op.drop_column('pillar_tool_pillar', 'type')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('pillar_tool_pillar', sa.Column('type', sa.VARCHAR(), autoincrement=False, nullable=False))
|
||||
op.drop_column('pillar_tool_pillar', 'parameter_type')
|
||||
# ### end Alembic commands ###
|
||||
@ -11,7 +11,7 @@ class Pillar(Base):
|
||||
id = Column(UUID, primary_key=True)
|
||||
pillar_name = Column(String, nullable=False)
|
||||
host_id = Column(UUID, ForeignKey('pillar_tool_host.id'), nullable=True)
|
||||
type = Column(String, nullable=False)
|
||||
parameter_type = Column(String, nullable=False)
|
||||
value = Column(String, nullable=False)
|
||||
|
||||
__table_args__ = (
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import json
|
||||
|
||||
import click
|
||||
import requests
|
||||
|
||||
@ -22,4 +24,38 @@ def pillar_get(fqdn):
|
||||
pillar_data = response.json()
|
||||
click.echo(pillar_data)
|
||||
except requests.exceptions.RequestException as e:
|
||||
click.echo(f"Error: {e}")
|
||||
click.echo(f"Error: {e}")
|
||||
|
||||
@pillar.command("set")
|
||||
@click.argument("name")
|
||||
@click.option("--host")
|
||||
@click.option("--hostgroup")
|
||||
@click.option("--parameter-type")
|
||||
@click.option("--value")
|
||||
def pillar_set(name: str, host: str | None, hostgroup: str | None, parameter_type: str | None, value: str | None):
|
||||
try:
|
||||
if parameter_type == 'str':
|
||||
pass # there is nothing to do here
|
||||
elif parameter_type == 'int':
|
||||
_ = int(value)
|
||||
elif parameter_type == 'float':
|
||||
_ = float(value)
|
||||
elif parameter_type == 'bool':
|
||||
_ = bool(value)
|
||||
elif parameter_type == 'list':
|
||||
value = json.loads(value)
|
||||
elif parameter_type == 'dict':
|
||||
value = json.loads(value)
|
||||
else:
|
||||
raise ValueError("Invalid parameter type")
|
||||
except ValueError as e:
|
||||
print(f"Failed to validate value: {e}")
|
||||
else:
|
||||
data = {
|
||||
'host': host,
|
||||
'hostgroup': hostgroup,
|
||||
'type': parameter_type,
|
||||
'value': json.dumps(value)
|
||||
}
|
||||
|
||||
requests.post(f"{base_url()}/pillar/{name}", headers=auth_header(), json=data)
|
||||
@ -1,18 +1,22 @@
|
||||
import json
|
||||
import uuid
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import select, insert, delete, bindparam
|
||||
from sqlalchemy.dialects.postgresql import insert
|
||||
from sqlalchemy import select, delete, bindparam
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.requests import Request
|
||||
from fastapi import APIRouter, Depends
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
from pillar_tool.db import Host
|
||||
from pillar_tool.db import Host, Pillar
|
||||
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.util.pillar_utilities import merge
|
||||
from pillar_tool.util.validation import validate_state_name, validate_fqdn
|
||||
from pillar_tool.util.validation import validate_state_name, validate_fqdn, validate_pillar_input_data, \
|
||||
split_and_validate_path
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/pillar",
|
||||
@ -59,21 +63,70 @@ def pillar_get(req: Request, fqdn: str):
|
||||
|
||||
@router.post("/{name}")
|
||||
def pillar_create(req: Request, name: str, params: PillarParams):
|
||||
db = req.state.db
|
||||
db: Session = req.state.db
|
||||
|
||||
# ensure that value and type have been set in the request parameters
|
||||
if params.type is None or params.value is None:
|
||||
return JSONResponse(status_code=400, content={
|
||||
'message': "Both parameter type and value need to be set!"
|
||||
})
|
||||
|
||||
# validate pillar data
|
||||
pillar_data = validate_pillar_input_data(params.value, params.type)
|
||||
|
||||
target_stmt = select(Host).where(Host.name == name)
|
||||
result = db.execute(target_stmt).fetchall()
|
||||
if params.host is not None:
|
||||
target_stmt = select(Host).where(Host.name == params.host)
|
||||
result = db.execute(target_stmt).fetchall()
|
||||
|
||||
if len(result) == 0:
|
||||
return JSONResponse(status_code=404, content={})
|
||||
print(name, result)
|
||||
|
||||
# this should be enforced by the database
|
||||
assert len(result) == 1
|
||||
target: Host = result[0][0]
|
||||
if len(result) == 0:
|
||||
return JSONResponse(status_code=404, content={})
|
||||
|
||||
# this should be enforced by the database
|
||||
assert len(result) == 1
|
||||
target: Host = result[0][0]
|
||||
elif params.hostgroup is not None:
|
||||
path = split_and_validate_path(params.hostgroup)
|
||||
last = None
|
||||
group_stmt = select(Host).where(Host.is_hostgroup == True and Host.parent_id == bindparam('parent') )
|
||||
else:
|
||||
return JSONResponse(status_code=400, content={'message': "Neither host nor hostgroup set"})
|
||||
|
||||
# if this is a dictionary value, parse it and create a separate entry for all the sub-pillars
|
||||
if type(pillar_data) == dict:
|
||||
def aux(prefix: str, input_value: dict) -> list[dict[str, str]]:
|
||||
out = []
|
||||
for key, value in input_value.items():
|
||||
if type(value) is dict:
|
||||
out += aux(f"{prefix}:{key}", value)
|
||||
else:
|
||||
out += [{
|
||||
'name': f"{prefix}:{key}",
|
||||
'type': type(value).__name__,
|
||||
'value': json.dumps(value)
|
||||
}]
|
||||
|
||||
return out
|
||||
|
||||
pillars_to_store = aux(name, pillar_data)
|
||||
|
||||
else:
|
||||
# build the pillar package
|
||||
pillars_to_store= [
|
||||
{ 'name': name, 'value': params.value, 'type': params.type }
|
||||
]
|
||||
|
||||
# store pillar data
|
||||
insert_stmt = insert(Pillar).values(id=bindparam('new_id'), host_id=target.id, pillar_name=bindparam('name'), parameter_type=bindparam('type'), value=bindparam('value'))
|
||||
upsert_stmt = insert_stmt.on_conflict_do_update(constraint='pillar_unique_pillar_name', set_={'parameter_type': bindparam('type'), 'value': bindparam('value')} )
|
||||
|
||||
for instance in pillars_to_store:
|
||||
instance['new_id'] = uuid4()
|
||||
result = db.execute(upsert_stmt, instance)
|
||||
print(result)
|
||||
|
||||
return JSONResponse(status_code=200, content={'message': 'ok'})
|
||||
|
||||
|
||||
|
||||
|
||||
@ -52,7 +52,8 @@ class StateParams(BaseModel):
|
||||
|
||||
# Pillar operations
|
||||
class PillarParams(BaseModel):
|
||||
target: str # must be host or hostgroup
|
||||
host: str | None
|
||||
hostgroup: str | None
|
||||
value: str | None # value if the pillar should be set
|
||||
type: str | None # type of pillar if pillar should be set
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
|
||||
@ -81,3 +82,28 @@ def split_and_validate_path(path: str) -> list[str] | None:
|
||||
|
||||
return labels
|
||||
|
||||
def type_from_name(data_type: str) -> type | None:
|
||||
match data_type:
|
||||
case 'int': return int
|
||||
case 'float': return float
|
||||
case 'str': return str
|
||||
case 'bool': return bool
|
||||
case 'dict': return dict
|
||||
case 'list': return list
|
||||
case _: raise ValueError(f"Invalid pillar input: Unsupported data type: {data_type}")
|
||||
|
||||
def name_from_type(value) -> str:
|
||||
if type(value) is int: return 'int'
|
||||
if type(value) is float: return 'float'
|
||||
if type(value) is str: return 'str'
|
||||
if type(value) is bool: return 'bool'
|
||||
if type(value) is dict: return 'dict'
|
||||
if type(value) is list: return 'list'
|
||||
raise ValueError(f"Invalid pillar input: Unsupported data type: {type(value)}")
|
||||
|
||||
def validate_pillar_input_data(value: str, data_type: str):
|
||||
decoded_data = json.loads(value)
|
||||
if type(decoded_data) is type_from_name(data_type):
|
||||
return decoded_data
|
||||
raise ValueError(f"Invalid pillar input: datatype does not match value")
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user