c34dab2cca
Build and Push Docker Container / build-and-push (push) Successful in 4m28s
- Create missing note files before running property:set. - Default date-only datetime values to midnight. - Keep property names out of path authorization checks. - Split policy, vault, daily note, and command target helpers. - Split n8n node args, description, output, and API response helpers. - Split API tests by auth, command, daily note, and property behavior. - Bump API and n8n node versions.
67 lines
1.9 KiB
Python
67 lines
1.9 KiB
Python
import asyncio, uuid, time
|
|
from pathlib import Path
|
|
|
|
from starlette.responses import JSONResponse
|
|
|
|
from app.config import CLI_BIN
|
|
from app.property_commands import prepare_property_args
|
|
|
|
def error(status_code:int, detail:str) -> JSONResponse:
|
|
return JSONResponse({"detail": detail}, status_code=status_code)
|
|
|
|
async def execute_command(args:list[str], cwd:Path, stdin:str|None, timeout:float) -> JSONResponse:
|
|
command_id = str(uuid.uuid4())
|
|
try:
|
|
args = prepare_property_args(args, cwd)
|
|
except PermissionError as exc:
|
|
return error(403, str(exc))
|
|
except OSError as exc:
|
|
return error(500, f"failed to prepare property command: {exc}")
|
|
|
|
command = [CLI_BIN, *args]
|
|
started = time.perf_counter()
|
|
|
|
try:
|
|
process = await asyncio.create_subprocess_exec(
|
|
*command,
|
|
cwd=str(cwd),
|
|
stdin=asyncio.subprocess.PIPE if stdin is not None else None,
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
except FileNotFoundError:
|
|
return error(500, f"configured CLI binary was not found: {CLI_BIN}")
|
|
|
|
try:
|
|
stdout_b, stderr_b = await asyncio.wait_for(
|
|
process.communicate(None if stdin is None else stdin.encode()),
|
|
timeout=timeout,
|
|
)
|
|
timed_out = False
|
|
except asyncio.TimeoutError:
|
|
process.kill()
|
|
stdout_b, stderr_b = await process.communicate()
|
|
timed_out = True
|
|
|
|
duration_ms = int((time.perf_counter() - started) * 1000)
|
|
stdout = stdout_b.decode(errors="replace").rstrip()
|
|
stderr = stderr_b.decode(errors="replace").rstrip()
|
|
exit_code = process.returncode if process.returncode is not None else -1
|
|
|
|
if timed_out:
|
|
exit_code = 124
|
|
stderr = (stderr + f"\nCommand timed out after {timeout}s").strip()
|
|
|
|
return JSONResponse(
|
|
{
|
|
"id": command_id,
|
|
"command": command,
|
|
"cwd": str(cwd),
|
|
"exit_code": exit_code,
|
|
"stdout": stdout,
|
|
"stderr": stderr,
|
|
"duration_ms": duration_ms,
|
|
"timed_out": timed_out,
|
|
}
|
|
)
|