Compare commits
3 Commits
89987f36b0
...
6fc2ac7969
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fc2ac7969 | |||
| 7f5e63e397 | |||
| 7553962f2b |
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,4 +3,5 @@ __build__/
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
/dist/
|
/dist/
|
||||||
/.idea/
|
/.idea/
|
||||||
|
/pillartool.egg-info/
|
||||||
pillar_tool.toml
|
pillar_tool.toml
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
"""Added environment assignments to database
|
||||||
|
|
||||||
|
Revision ID: dd573f631ee4
|
||||||
|
Revises: e33744090598
|
||||||
|
Create Date: 2026-02-10 21:47:33.493901
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'dd573f631ee4'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = 'e33744090598'
|
||||||
|
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! ###
|
||||||
|
pass
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
pass
|
||||||
|
# ### end Alembic commands ###
|
||||||
@ -15,6 +15,17 @@ class Environment(Base):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EnvironmentAssignment(Base):
|
||||||
|
__tablename__ = "pillar_tool_environment_assignment"
|
||||||
|
|
||||||
|
environment_id = Column(UUID, ForeignKey("pillar_tool_environment.id"), nullable=False)
|
||||||
|
host_id = Column(UUID, ForeignKey("pillar_tool_host.id"), nullable=False)
|
||||||
|
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint('environment_id', 'host_id', name="pillar_tool_unique_environment_assignment")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class State(Base):
|
class State(Base):
|
||||||
__tablename__ = "pillar_tool_state"
|
__tablename__ = "pillar_tool_state"
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,14 @@
|
|||||||
# load config so everything else can work
|
from pillar_tool.schemas import HealthCheckError
|
||||||
from pillar_tool.util import load_config, config
|
from pillar_tool.util import load_config, config
|
||||||
from pillar_tool.util.validation import validate_and_split_path_and_domain_name
|
|
||||||
|
|
||||||
load_config()
|
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.middleware.logging import request_logging_middleware
|
||||||
from pillar_tool.schemas import HostCreateParams
|
|
||||||
|
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
|
|
||||||
from pillar_tool.db.database import get_connection
|
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 pillar_tool.db.queries.auth_queries import create_user
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from starlette.middleware.authentication import AuthenticationMiddleware
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||||
@ -27,6 +20,8 @@ from pillar_tool.middleware.basicauth_backend import BasicAuthBackend
|
|||||||
from pillar_tool.middleware.db_connection import db_connection_middleware
|
from pillar_tool.middleware.db_connection import db_connection_middleware
|
||||||
from pillar_tool.db.database import run_db_migrations
|
from pillar_tool.db.database import run_db_migrations
|
||||||
|
|
||||||
|
# import all the routers
|
||||||
|
from pillar_tool.routers.host import router as host_router
|
||||||
|
|
||||||
# run any pending migrations
|
# run any pending migrations
|
||||||
run_db_migrations()
|
run_db_migrations()
|
||||||
@ -59,6 +54,7 @@ def on_db_error(request: Request, exc: Exception):
|
|||||||
|
|
||||||
def on_general_error(request: Request, exc: Exception):
|
def on_general_error(request: Request, exc: Exception):
|
||||||
print("wtf?")
|
print("wtf?")
|
||||||
|
|
||||||
response = PlainTextResponse(str(exc), status_code=500)
|
response = PlainTextResponse(str(exc), status_code=500)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@ -68,17 +64,27 @@ 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
|
||||||
|
app.include_router(host_router)
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
return {"message": "Hello World"}
|
return {"message": "Hello World"}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
async def health():
|
async def health():
|
||||||
# TODO: improve health check
|
# Check database connection
|
||||||
return {"message": "Healthy"}
|
try:
|
||||||
|
db = get_connection()
|
||||||
|
db.execute("SELECT 1")
|
||||||
|
db.close()
|
||||||
|
except Exception as e:
|
||||||
|
return HealthCheckError(500, f"Database connection error:\n{e}").response()
|
||||||
|
|
||||||
|
return HealthCheckSuccess().response()
|
||||||
|
|
||||||
|
"""
|
||||||
@app.get("/pillar/{host}")
|
@app.get("/pillar/{host}")
|
||||||
async def pillar_get(req: Request, host: str):
|
async def pillar_get(req: Request, host: str):
|
||||||
print(req.headers)
|
print(req.headers)
|
||||||
@ -96,19 +102,59 @@ async def pillar_set(request: Request, host: str, value: str):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: list, create update and delete hosts
|
||||||
@app.get("/hosts")
|
@app.get("/hosts")
|
||||||
async def host_list(request: Request):
|
async def host_list(request: Request):
|
||||||
all_hosts = list_all_hosts(request.state.db)
|
all_hosts = list_all_hosts(request.state.db)
|
||||||
return JSONResponse([x.name for x in all_hosts if x.parent_id is None])
|
return JSONResponse([x.name for x in all_hosts if not x.is_hostgroup])
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: list, create, update and delete hostgroups
|
||||||
@app.get("/hostgroups")
|
@app.get("/hostgroups")
|
||||||
async def hostgroup_list(request: Request):
|
async def hostgroup_list(request: Request):
|
||||||
all_hosts = list_all_hosts(request.state.db)
|
all_hosts = list_all_hosts(request.state.db)
|
||||||
return JSONResponse([x.name for x in all_hosts if x.parent_id is not None])
|
return JSONResponse([x.name for x in all_hosts if x.is_hostgroup])
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: list, create, update and delete states
|
||||||
|
# TODO: list, create, update and delete environments
|
||||||
|
|
||||||
|
# TODO: top files generated on a per host basis
|
||||||
@app.get("/top/{fqdn}")
|
@app.get("/top/{fqdn}")
|
||||||
async def host_top(request: Request, fqdn: str):
|
async def host_top(req: Request, fqdn: str):
|
||||||
|
db: Session = req.state.db
|
||||||
|
|
||||||
|
if not validate_fqdn(fqdn):
|
||||||
|
return JSONResponse(status_code=400, content={
|
||||||
|
'message': f"Invalid FQDN: {fqdn}"
|
||||||
|
})
|
||||||
|
|
||||||
|
environment_stmt = select(Environment)
|
||||||
|
result = db.execute(environment_stmt).fetchall()
|
||||||
|
|
||||||
|
if len(result) == 0:
|
||||||
|
return JSONResponse(status_code=400, content={
|
||||||
|
'message': "There are no environments defined"
|
||||||
|
})
|
||||||
|
|
||||||
|
environments: list[Environment] = list(map(lambda x: x[0], result))
|
||||||
|
|
||||||
|
|
||||||
|
stmt_host = select(Host).where(Host.name == fqdn)
|
||||||
|
result = db.execute(stmt_host).fetchall()
|
||||||
|
if len(result) < 1:
|
||||||
|
return JSONResponse(status_code=404, content={
|
||||||
|
'message': f"No such Host is known: {fqdn}"
|
||||||
|
})
|
||||||
|
|
||||||
|
# this should be enforced by the database
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
host: Host = result[0][0]
|
||||||
|
|
||||||
|
stmt_top = select(Environment, Host, State).where(Environment).join
|
||||||
# TODO: implement
|
# TODO: implement
|
||||||
return JSONResponse({})
|
return JSONResponse({})
|
||||||
|
|
||||||
|
"""
|
||||||
1
pillar_tool/ptcli/__init__.py
Normal file
1
pillar_tool/ptcli/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .main import main
|
||||||
@ -3,9 +3,10 @@ import base64
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from click import Context
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
cfg: Config | None = None
|
cfg: Config | None = None
|
||||||
base_url: str | None = None
|
base_url: str | None = None
|
||||||
@ -39,6 +40,14 @@ def pillar():
|
|||||||
def host():
|
def host():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@main.group("hostgroup")
|
||||||
|
def hostgroup():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@main.group("environment")
|
||||||
|
def environment():
|
||||||
|
pass
|
||||||
|
|
||||||
@main.group("query")
|
@main.group("query")
|
||||||
def query():
|
def query():
|
||||||
pass
|
pass
|
||||||
@ -55,7 +64,7 @@ def pillar_list():
|
|||||||
def host_list():
|
def host_list():
|
||||||
click.echo("Listing known hosts...")
|
click.echo("Listing known hosts...")
|
||||||
try:
|
try:
|
||||||
response = requests.get(f"{base_url}/hosts", headers=auth_header)
|
response = requests.get(f"{base_url}/host", headers=auth_header)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
print(response.json())
|
print(response.json())
|
||||||
@ -68,16 +77,48 @@ def host_list():
|
|||||||
def host_create(fqdn: str, parent: str | None):
|
def host_create(fqdn: str, parent: str | None):
|
||||||
click.echo("Creating host...")
|
click.echo("Creating host...")
|
||||||
try:
|
try:
|
||||||
response = requests.post(f"{base_url}/host/{fqdn}", json={'parent': parent}, headers=auth_header)
|
params = HostCreateParams(
|
||||||
|
parent=parent
|
||||||
|
)
|
||||||
|
response = requests.post(f"{base_url}/host/{fqdn}", json=params.model_dump(), 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 create host:\n{e}")
|
raise click.ClickException(f"Failed to create host:\n{e}")
|
||||||
|
|
||||||
|
|
||||||
click.echo(f"Host '{fqdn}' created!")
|
click.echo(f"Host '{fqdn}' created!")
|
||||||
|
|
||||||
|
|
||||||
@host.command("delete")
|
@host.command("delete")
|
||||||
@click.argument("full_path")
|
@click.argument("fqdn")
|
||||||
def host_delete(full_path: str):
|
def host_delete(fqdn: str):
|
||||||
click.confirm(f"Are you sure you want to delete")
|
if not validate_fqdn(fqdn):
|
||||||
|
click.echo("Invalid FQDN")
|
||||||
|
return
|
||||||
|
|
||||||
|
if click.confirm(f"Are you sure you want to delete '{fqdn}'?"):
|
||||||
|
click.echo("Deleting host...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.delete(f'{base_url}/host/{fqdn}', headers=auth_header)
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
raise click.ClickException(f"Failed to delete host:\n{e}")
|
||||||
|
|
||||||
|
|
||||||
|
@hostgroup.command("list")
|
||||||
|
def hostgroup_list():
|
||||||
|
click.echo("Listing known hostgroups...")
|
||||||
|
click.echo("TODO: implement")
|
||||||
|
|
||||||
|
|
||||||
|
@hostgroup.command("create")
|
||||||
|
@click.argument("path")
|
||||||
|
def hostgroup_create(path: str):
|
||||||
|
click.echo("TODO: implement")
|
||||||
|
|
||||||
|
|
||||||
|
@hostgroup.command("delete")
|
||||||
|
@click.argument("path")
|
||||||
|
def hostgroup_delete(path: str):
|
||||||
|
click.echo("TODO: implement")
|
||||||
|
|
||||||
@ -18,24 +18,109 @@ router = APIRouter(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: check comments in this file (they are written by AI)
|
||||||
@router.get("")
|
@router.get("")
|
||||||
def hosts_get(req: Request):
|
def hosts_get(req: Request):
|
||||||
|
"""
|
||||||
|
Retrieve a list of all hosts (excluding hostgroups) in the system.
|
||||||
|
|
||||||
|
Queries the database for all host entries where is_hostgroup is False,
|
||||||
|
returning only the names of the hosts in a flat list format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
req: FastAPI request object containing database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSONResponse containing a list of host names as strings
|
||||||
|
"""
|
||||||
db: Session = req.state.db
|
db: Session = req.state.db
|
||||||
|
|
||||||
|
result = db.execute(select(Host).where(Host.is_hostgroup == False)).fetchall()
|
||||||
|
hosts: list[Host] = list(map(lambda x: x[0], result))
|
||||||
|
|
||||||
|
return JSONResponse(status_code=200, content=list(map(lambda x: x.name, hosts)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{fqdn}")
|
@router.get("/{fqdn}")
|
||||||
def host_get(req: Request, fqdn: str):
|
def host_get(req: Request, fqdn: str):
|
||||||
|
"""
|
||||||
|
Retrieve detailed information about a specific host including its hierarchical path.
|
||||||
|
|
||||||
|
Fetches host details from the database and constructs the full path by traversing
|
||||||
|
parent relationships up to the root. Returns both the host name and its complete
|
||||||
|
hierarchical path as a slash-separated string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
req: FastAPI request object containing database session
|
||||||
|
fqdn: Fully qualified domain name of the host to retrieve
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSONResponse containing host name and hierarchical path
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: If FQDN format is invalid or host is not found
|
||||||
|
"""
|
||||||
db: Session = req.state.db
|
db: Session = req.state.db
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{fqdn}")
|
|
||||||
async def host_add(request: Request, fqdn: str, params: HostCreateParams):
|
|
||||||
db: Session = request.state.db
|
|
||||||
|
|
||||||
if not validate_fqdn(fqdn):
|
if not validate_fqdn(fqdn):
|
||||||
raise HTTPException(status_code=400, detail="Provided host is not an FQDN")
|
raise HTTPException(status_code=400, detail="Provided host is not an FQDN")
|
||||||
|
|
||||||
|
host_stmt = select(Host).where(Host.name == fqdn)
|
||||||
|
result = db.execute(host_stmt).fetchall()
|
||||||
|
|
||||||
|
if len(result) != 1:
|
||||||
|
raise HTTPException(status_code=404, detail=f"No such host found (length of result was {len(result)})")
|
||||||
|
|
||||||
|
host: Host = result[0][0]
|
||||||
|
|
||||||
|
last_parent = host
|
||||||
|
path = []
|
||||||
|
parent_stmt = select(Host).where(Host.id == bindparam('parent_id'))
|
||||||
|
while host.parent_id is not None:
|
||||||
|
result = db.execute(parent_stmt, { 'parent_id': last_parent.parent_id }).fetchall()
|
||||||
|
|
||||||
|
# Note: this assertion should be enforced by the database
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
parent = result[0][0]
|
||||||
|
path.append(parent)
|
||||||
|
last_parent = parent
|
||||||
|
|
||||||
|
path.reverse()
|
||||||
|
return JSONResponse(status_code=200, content={
|
||||||
|
"host": host.name,
|
||||||
|
"path": '/'.join(map(lambda x: x.name, path))
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{fqdn}")
|
||||||
|
async def host_add(request: Request, fqdn: str, params: HostCreateParams):
|
||||||
|
"""
|
||||||
|
Create a new host with optional parent hierarchy.
|
||||||
|
|
||||||
|
Validates FQDN format and parent path structure before creating the host.
|
||||||
|
If a parent is specified, ensures all parent components exist in the database.
|
||||||
|
Creates the host with a unique UUID and proper hierarchical relationships.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: FastAPI request object containing database session
|
||||||
|
fqdn: Fully qualified domain name for the new host
|
||||||
|
params: Host creation parameters including optional parent path
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSONResponse with creation details and host information
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: If FQDN format is invalid or parent doesn't exist
|
||||||
|
"""
|
||||||
|
db: Session = request.state.db
|
||||||
|
|
||||||
|
# Validate that the provided FQDN is properly formatted
|
||||||
|
if not validate_fqdn(fqdn):
|
||||||
|
raise HTTPException(status_code=400, detail="Provided host is not an FQDN")
|
||||||
|
|
||||||
|
# Process parent path if provided
|
||||||
if params.parent is not None:
|
if params.parent is not None:
|
||||||
parent_labels = split_and_validate_path(params.parent)
|
parent_labels = split_and_validate_path(params.parent)
|
||||||
if parent_labels is None:
|
if parent_labels is None:
|
||||||
@ -43,6 +128,7 @@ async def host_add(request: Request, fqdn: str, params: HostCreateParams):
|
|||||||
else:
|
else:
|
||||||
parent_labels = []
|
parent_labels = []
|
||||||
|
|
||||||
|
# Traverse the parent hierarchy to ensure all components exist
|
||||||
parent_id = None
|
parent_id = None
|
||||||
stmt_select_respecting_parent = select(Host).where(Host.name == bindparam("label") and Host.parent_id == bindparam("parent_id"))
|
stmt_select_respecting_parent = select(Host).where(Host.name == bindparam("label") and Host.parent_id == bindparam("parent_id"))
|
||||||
for label in parent_labels:
|
for label in parent_labels:
|
||||||
@ -59,6 +145,7 @@ async def host_add(request: Request, fqdn: str, params: HostCreateParams):
|
|||||||
|
|
||||||
parent_id = result[0][0].parent_id
|
parent_id = result[0][0].parent_id
|
||||||
|
|
||||||
|
# Create new host with unique ID and hierarchical structure
|
||||||
new_host = Host(
|
new_host = Host(
|
||||||
id=uuid.uuid4(),
|
id=uuid.uuid4(),
|
||||||
name=fqdn,
|
name=fqdn,
|
||||||
|
|||||||
@ -1,7 +1,45 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from starlette.responses import JSONResponse
|
||||||
|
|
||||||
|
|
||||||
|
# Error returns
|
||||||
|
class ErrorDetails(BaseModel):
|
||||||
|
status: str
|
||||||
|
message: str
|
||||||
|
|
||||||
|
class ErrorReturn(BaseModel):
|
||||||
|
status_code: int
|
||||||
|
content: ErrorDetails
|
||||||
|
|
||||||
|
def response(self):
|
||||||
|
return JSONResponse(self.model_dump())
|
||||||
|
|
||||||
|
class HealthCheckError(ErrorReturn):
|
||||||
|
def __init__(self, code: int, reason: str):
|
||||||
|
super().__init__(**{
|
||||||
|
'status_code': code,
|
||||||
|
'content': ErrorDetails(
|
||||||
|
status="error",
|
||||||
|
message=f"Host is not healthy: {reason}"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
class HealthCheckSuccess(ErrorReturn):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(**{
|
||||||
|
'status_code': 200,
|
||||||
|
'content': ErrorDetails(
|
||||||
|
status='ok',
|
||||||
|
message='API is healthy'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# Host operations
|
||||||
class HostCreateParams(BaseModel):
|
class HostCreateParams(BaseModel):
|
||||||
parent: str | None
|
parent: str | None
|
||||||
|
|
||||||
|
# Hostgroup operations
|
||||||
class HostgroupCreateParams(BaseModel):
|
class HostgroupCreateParams(BaseModel):
|
||||||
parent: str | None
|
parent: str | None
|
||||||
|
|
||||||
|
|||||||
@ -11,4 +11,5 @@ Requires-Dist: jinja2>=3.1.6
|
|||||||
Requires-Dist: psycopg2>=2.9.11
|
Requires-Dist: psycopg2>=2.9.11
|
||||||
Requires-Dist: pycryptodome>=3.23.0
|
Requires-Dist: pycryptodome>=3.23.0
|
||||||
Requires-Dist: pydantic>=2.12.5
|
Requires-Dist: pydantic>=2.12.5
|
||||||
|
Requires-Dist: requests>=2.32.5
|
||||||
Requires-Dist: sqlalchemy>=2.0.45
|
Requires-Dist: sqlalchemy>=2.0.45
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
pyproject.toml
|
pyproject.toml
|
||||||
pillar_tool/__init__.py
|
pillar_tool/__init__.py
|
||||||
pillar_tool/main.py
|
pillar_tool/main.py
|
||||||
pillar_tool/ptcli.py
|
pillar_tool/schemas.py
|
||||||
pillar_tool/db/__init__.py
|
pillar_tool/db/__init__.py
|
||||||
pillar_tool/db/base_model.py
|
pillar_tool/db/base_model.py
|
||||||
pillar_tool/db/database.py
|
pillar_tool/db/database.py
|
||||||
@ -14,19 +14,35 @@ pillar_tool/db/migrations/versions/2025_12_27_1159-4cc7f4e295f1_added_unique_to_
|
|||||||
pillar_tool/db/migrations/versions/2025_12_27_1958-678356102624_added_pillar_structure.py
|
pillar_tool/db/migrations/versions/2025_12_27_1958-678356102624_added_pillar_structure.py
|
||||||
pillar_tool/db/migrations/versions/2025_12_30_1009-c6fe061ad732_better_uniqueness_contraints.py
|
pillar_tool/db/migrations/versions/2025_12_30_1009-c6fe061ad732_better_uniqueness_contraints.py
|
||||||
pillar_tool/db/migrations/versions/2026_01_01_1503-54537e95fc4d_coupled_host_and_hostgroup_into_single_.py
|
pillar_tool/db/migrations/versions/2026_01_01_1503-54537e95fc4d_coupled_host_and_hostgroup_into_single_.py
|
||||||
|
pillar_tool/db/migrations/versions/2026_02_08_2034-7eb66922e256_pillars_are_directly_assigned_to_the_.py
|
||||||
|
pillar_tool/db/migrations/versions/2026_02_08_2051-0a912926be8b_added_environments_and_states_to_the_db_.py
|
||||||
|
pillar_tool/db/migrations/versions/2026_02_08_2220-e33744090598_mark_hosts_as_hostgroups.py
|
||||||
|
pillar_tool/db/migrations/versions/2026_02_10_2147-dd573f631ee4_added_environment_assignments_to_.py
|
||||||
pillar_tool/db/models/__init__.py
|
pillar_tool/db/models/__init__.py
|
||||||
pillar_tool/db/models/pillar_data.py
|
pillar_tool/db/models/pillar_data.py
|
||||||
|
pillar_tool/db/models/top_data.py
|
||||||
pillar_tool/db/models/user.py
|
pillar_tool/db/models/user.py
|
||||||
pillar_tool/db/queries/__init__.py
|
pillar_tool/db/queries/__init__.py
|
||||||
pillar_tool/db/queries/auth_queries.py
|
pillar_tool/db/queries/auth_queries.py
|
||||||
|
pillar_tool/db/queries/host_queries.py
|
||||||
pillar_tool/db/queries/pillar_queries.py
|
pillar_tool/db/queries/pillar_queries.py
|
||||||
pillar_tool/frontend/__init__.py
|
pillar_tool/frontend/__init__.py
|
||||||
pillar_tool/frontend/pillar_view.py
|
pillar_tool/frontend/pillar_view.py
|
||||||
pillar_tool/middleware/__init__.py
|
pillar_tool/middleware/__init__.py
|
||||||
pillar_tool/middleware/basicauth_backend.py
|
pillar_tool/middleware/basicauth_backend.py
|
||||||
pillar_tool/middleware/db_connection.py
|
pillar_tool/middleware/db_connection.py
|
||||||
|
pillar_tool/middleware/logging.py
|
||||||
|
pillar_tool/ptcli/__init__.py
|
||||||
|
pillar_tool/ptcli/main.py
|
||||||
|
pillar_tool/routers/__init__.py
|
||||||
|
pillar_tool/routers/environment.py
|
||||||
|
pillar_tool/routers/host.py
|
||||||
|
pillar_tool/routers/hostgroup.py
|
||||||
|
pillar_tool/routers/pillar.py
|
||||||
|
pillar_tool/routers/state.py
|
||||||
pillar_tool/util/__init__.py
|
pillar_tool/util/__init__.py
|
||||||
pillar_tool/util/config.py
|
pillar_tool/util/config.py
|
||||||
|
pillar_tool/util/validation.py
|
||||||
pillartool.egg-info/PKG-INFO
|
pillartool.egg-info/PKG-INFO
|
||||||
pillartool.egg-info/SOURCES.txt
|
pillartool.egg-info/SOURCES.txt
|
||||||
pillartool.egg-info/dependency_links.txt
|
pillartool.egg-info/dependency_links.txt
|
||||||
|
|||||||
@ -6,4 +6,5 @@ jinja2>=3.1.6
|
|||||||
psycopg2>=2.9.11
|
psycopg2>=2.9.11
|
||||||
pycryptodome>=3.23.0
|
pycryptodome>=3.23.0
|
||||||
pydantic>=2.12.5
|
pydantic>=2.12.5
|
||||||
|
requests>=2.32.5
|
||||||
sqlalchemy>=2.0.45
|
sqlalchemy>=2.0.45
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user