Replaces LICENSE (GPLv3 -> AGPLv3) and prepends `SPDX-License-Identifier: AGPL-3.0-or-later` to every source file across decnet/, decnet_web/, tests/, scripts/, and tools/. Rationale: closes the GPLv3 ASP loophole so any party operating a modified DECNET as a network service must offer their modified source. Personal copyright (Samuel Paschuan) + inbound=outbound contributions make a future unilateral relicense infeasible. - LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt) - COPYRIGHT: project copyright notice - tools/add_spdx_headers.py: idempotent header injector (shebang- and PEP 263-aware) Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh). No behavior change; comments only.
68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""Shared validation for the ``mode`` / ``target_host_uuid`` pair.
|
|
|
|
Called by the two topology-create endpoints
|
|
(``api_create_topology``, ``api_create_blank_topology``). Kept as a
|
|
tiny module so the rules stay in one place when Step 6 grows the list
|
|
(e.g. when we start rejecting hosts that already own a topology).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Optional
|
|
|
|
from fastapi import HTTPException
|
|
|
|
# Hosts we're willing to route a new topology to. ``enrolled`` is fine
|
|
# because the agent process has certs and will answer mTLS calls as
|
|
# soon as it's up; ``active`` means we've seen a heartbeat recently.
|
|
_ROUTABLE_HOST_STATUSES = {"enrolled", "active"}
|
|
|
|
|
|
async def validate_target_host(
|
|
repo: Any,
|
|
mode: str,
|
|
target_host_uuid: Optional[str],
|
|
) -> None:
|
|
"""Raise HTTPException(400) if the mode/host combination is invalid.
|
|
|
|
Rules:
|
|
- ``mode=="unihost"`` with a ``target_host_uuid`` → 400 (nonsense).
|
|
- ``mode=="agent"`` without ``target_host_uuid`` → 400.
|
|
- ``mode=="agent"`` with an unknown uuid → 400.
|
|
- ``mode=="agent"`` pointing at a host in ``unreachable`` /
|
|
``decommissioned`` → 400 (operator asked for a broken path).
|
|
"""
|
|
if mode == "unihost":
|
|
if target_host_uuid is not None:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="target_host_uuid is only valid when mode='agent'",
|
|
)
|
|
return
|
|
|
|
if mode == "agent":
|
|
if not target_host_uuid:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="mode='agent' requires target_host_uuid",
|
|
)
|
|
host = await repo.get_swarm_host_by_uuid(target_host_uuid)
|
|
if host is None:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"unknown swarm host {target_host_uuid!r}",
|
|
)
|
|
if host.get("status") not in _ROUTABLE_HOST_STATUSES:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=(
|
|
f"swarm host {target_host_uuid!r} is "
|
|
f"{host.get('status')!r}; expected one of "
|
|
f"{sorted(_ROUTABLE_HOST_STATUSES)}"
|
|
),
|
|
)
|
|
return
|
|
|
|
# Shouldn't happen — the pydantic pattern should have rejected it.
|
|
raise HTTPException(status_code=400, detail=f"unknown mode {mode!r}")
|