Workspace an der OE, Distro-Konfliktregel, Ziel-OE fuer Installationsmedien
- PUT /api/v1/organisationseinheiten/{id}/workspace: Workspace (sichtbar fuer
die Organisation, mit Distro) an eine OE haengen, automatisch oder auf eine
Version gepinnt.
- distro_konflikte(): simuliert OE-Workspace, OE umhaengen und Geraet
verschieben und blockiert, wenn ein aktives Geraet danach einen Workspace
mit anderer Distro bekaeme (Meldung nennt Geraete und OE).
- Sicherheitsnetz in geraete_basis_herkunft(): passt der OE-Workspace nicht
zur Geraete-Distro, gilt der Workspace der Installation.
- Enrollment-Sessions mit Ziel-OE (geprueft gegen die Profil-Distro);
aktivierte Geraete landen dort, resolve_template liefert die Blueprints der
wirksamen Grundausstattung.
- Profil anlegen prueft, dass Workspace und Profil dieselbe Distro haben.
- OE-Liste mit eigenem und wirksamem (geerbtem) Workspace, Geraeteliste mit
wirksamem Workspace und Distro.
Workspace-Creator neu, Schritt F.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5187ad7be7
commit
1288097927
1 file changed
+355
-60
@@ -320,6 +320,13 @@ class CreateEnrollmentSessionRequest(BaseModel):
|
||||
max_devices: int
|
||||
expires_at: str
|
||||
neuinstallation_ohne_rueckfrage: bool = False
|
||||
ziel_oe_id: str | None = None
|
||||
|
||||
|
||||
class SetOeWorkspaceRequest(BaseModel):
|
||||
workspace_id: str | None = None
|
||||
version_id: str | None = None
|
||||
automatisch: bool = True
|
||||
|
||||
|
||||
class RechargeEnrollmentSessionRequest(BaseModel):
|
||||
@@ -653,7 +660,7 @@ def find_enrollment_session(code: str):
|
||||
"""
|
||||
SELECT es.id, es.organization_id, o.name, es.bereitstellungsvorlage_id,
|
||||
es.max_devices, es.devices_used, es.expires_at, es.revoked_at,
|
||||
es.neuinstallation_ohne_rueckfrage
|
||||
es.neuinstallation_ohne_rueckfrage, es.ziel_oe_id
|
||||
FROM enrollment_sessions es
|
||||
JOIN organizations o ON o.id = es.organization_id
|
||||
WHERE es.id = %s
|
||||
@@ -667,7 +674,7 @@ def find_enrollment_session(code: str):
|
||||
|
||||
(
|
||||
session_id, organization_id, organization_name, bereitstellungsvorlage_id,
|
||||
max_devices, devices_used, expires_at, revoked_at, neuinstallation_ohne_rueckfrage,
|
||||
max_devices, devices_used, expires_at, revoked_at, neuinstallation_ohne_rueckfrage, ziel_oe_id,
|
||||
) = row
|
||||
|
||||
if revoked_at is not None:
|
||||
@@ -683,8 +690,25 @@ def find_enrollment_session(code: str):
|
||||
"organization_name": organization_name,
|
||||
"bereitstellungsvorlage_id": str(bereitstellungsvorlage_id),
|
||||
"neuinstallation_ohne_rueckfrage": bool(neuinstallation_ohne_rueckfrage),
|
||||
"ziel_oe_id": str(ziel_oe_id) if ziel_oe_id else None,
|
||||
}
|
||||
|
||||
def geraet_in_ziel_oe(device_id: str, ziel_oe_id: str) -> None:
|
||||
"""Gerät aus einem Installationsmedium mit Ziel-OE dorthin setzen
|
||||
(Schritt F) - auch bei einer Neuinstallation eines bekannten Geräts:
|
||||
wer mit diesem Stick installiert, will es dort haben. Die Distro-
|
||||
Verträglichkeit wurde beim Erstellen des Mediums geprüft."""
|
||||
|
||||
with get_database_connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE devices SET oe_id = %s
|
||||
WHERE id = %s AND organization_id = (SELECT organization_id FROM organisationseinheiten WHERE id = %s)
|
||||
""",
|
||||
(ziel_oe_id, device_id, ziel_oe_id),
|
||||
)
|
||||
|
||||
def consume_enrollment_session(session_id: str) -> None:
|
||||
with get_database_connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -1355,6 +1379,19 @@ def geraete_basis_herkunft(cur, device_id: str):
|
||||
while aktuell is not None and aktuell in oes:
|
||||
parent_id, workspace_id, version_id, automatisch = oes[aktuell]
|
||||
if workspace_id is not None:
|
||||
# Sicherheitsnetz (Schritt F): passt die Distro des
|
||||
# OE-Workspace nicht zur Distro des Geräts (z. B. nach einer
|
||||
# Neuinstallation mit anderer Distro), gilt der Workspace aus
|
||||
# der Installation - statt still fehlender Apps. Zuweisungen
|
||||
# und Verschiebungen verhindern diesen Fall sonst vorab
|
||||
# (distro_konflikte()).
|
||||
cur.execute("SELECT backend_id FROM workspaces WHERE id = %s", (workspace_id,))
|
||||
ws_backend = cur.fetchone()[0]
|
||||
if ws_backend is not None and ws_backend != backend_id:
|
||||
return (
|
||||
veroeffentlichte_version_id(cur, installations_workspace_id), backend_id,
|
||||
{"art": "installation", "oe_konflikt": aktuell},
|
||||
)
|
||||
automatisch = bool(automatisch or version_id is None)
|
||||
if automatisch:
|
||||
version_id = veroeffentlichte_version_id(cur, workspace_id)
|
||||
@@ -1645,22 +1682,46 @@ def fetch_organisationseinheiten(organization_id: str):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, parent_id, name, ist_standard
|
||||
FROM organisationseinheiten
|
||||
WHERE organization_id = %s
|
||||
ORDER BY name
|
||||
SELECT oe.id, oe.parent_id, oe.name, oe.ist_standard,
|
||||
oe.workspace_id, w.name, b.name, b.version, oe.automatisch_aktualisieren, v.nummer
|
||||
FROM organisationseinheiten oe
|
||||
LEFT JOIN workspaces w ON w.id = oe.workspace_id
|
||||
LEFT JOIN backends b ON b.id = w.backend_id
|
||||
LEFT JOIN workspace_versionen v ON v.id = oe.workspace_version_id
|
||||
WHERE oe.organization_id = %s
|
||||
ORDER BY oe.name
|
||||
""",
|
||||
(organization_id,),
|
||||
)
|
||||
return [
|
||||
{
|
||||
"id": str(oe_id),
|
||||
"parent_id": str(parent_id) if parent_id is not None else None,
|
||||
"name": name,
|
||||
"ist_standard": ist_standard,
|
||||
}
|
||||
for oe_id, parent_id, name, ist_standard in cur.fetchall()
|
||||
]
|
||||
zeilen = cur.fetchall()
|
||||
|
||||
oes = [
|
||||
{
|
||||
"id": str(oe_id),
|
||||
"parent_id": str(parent_id) if parent_id is not None else None,
|
||||
"name": name,
|
||||
"ist_standard": ist_standard,
|
||||
# Workspace-Creator neu, Schritt F: eigener Workspace der OE
|
||||
# (oder None) und der wirksame, ggf. geerbte.
|
||||
"workspace": (
|
||||
{"id": str(ws_id), "name": ws_name, "distro": f"{b_name} {b_version}",
|
||||
"automatisch": automatisch or nummer is None, "version": nummer}
|
||||
if ws_id else None
|
||||
),
|
||||
}
|
||||
for oe_id, parent_id, name, ist_standard, ws_id, ws_name, b_name, b_version, automatisch, nummer in zeilen
|
||||
]
|
||||
nach_id = {oe["id"]: oe for oe in oes}
|
||||
for oe in oes:
|
||||
aktuell, gesehen = oe, set()
|
||||
while aktuell is not None and aktuell["workspace"] is None and aktuell["id"] not in gesehen:
|
||||
gesehen.add(aktuell["id"])
|
||||
aktuell = nach_id.get(aktuell["parent_id"])
|
||||
oe["wirksamer_workspace"] = (
|
||||
{**aktuell["workspace"], "geerbt_von": None if aktuell is oe else aktuell["name"]}
|
||||
if aktuell is not None and aktuell["workspace"] else None
|
||||
)
|
||||
return oes
|
||||
|
||||
def create_organisationseinheit(organization_id: str, name: str, parent_id: str | None):
|
||||
"""
|
||||
@@ -1734,6 +1795,10 @@ def update_organisationseinheit(oe_id: str, name: str, parent_id: str | None):
|
||||
return None
|
||||
current = parent_by_id.get(current)
|
||||
|
||||
konflikte = distro_konflikte(cur, str(organization_id), oe_parent={oe_id: parent_id})
|
||||
if konflikte:
|
||||
return {"konflikt": konflikt_meldung(konflikte)}
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE organisationseinheiten
|
||||
@@ -1897,15 +1962,139 @@ def move_device_to_oe(device_id: str, oe_id: str):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE devices
|
||||
SET oe_id = %s
|
||||
WHERE id = %s
|
||||
AND organization_id = (SELECT organization_id FROM organisationseinheiten WHERE id = %s)
|
||||
RETURNING id
|
||||
SELECT d.organization_id FROM devices d JOIN organisationseinheiten oe ON oe.id = %s
|
||||
WHERE d.id = %s AND d.organization_id = oe.organization_id
|
||||
""",
|
||||
(oe_id, device_id, oe_id),
|
||||
(oe_id, device_id),
|
||||
)
|
||||
return cur.fetchone() is not None
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
return False
|
||||
konflikte = distro_konflikte(cur, str(row[0]), geraet_oe={device_id: oe_id})
|
||||
if konflikte:
|
||||
return konflikt_meldung(konflikte)
|
||||
cur.execute("UPDATE devices SET oe_id = %s WHERE id = %s", (oe_id, device_id))
|
||||
return True
|
||||
|
||||
# --- Workspace an der OE und Distro-Konfliktregel (Schritt F, 07.10.2026) ---
|
||||
|
||||
def distro_konflikte(cur, organization_id, oe_workspace=None, oe_parent=None, geraet_oe=None) -> list[dict]:
|
||||
"""
|
||||
Simuliert eine Änderung und meldet jedes aktive Gerät der Organisation,
|
||||
das danach einen Workspace mit anderer Distro bekäme als seine eigene.
|
||||
oe_workspace: {oe_id: workspace_id oder None}, oe_parent: {oe_id:
|
||||
parent_id}, geraet_oe: {device_id: oe_id}. Grundlage der Regel
|
||||
"blockieren mit klarer Meldung" (ADR-0038) - eine andere Distro hieße
|
||||
Neuinstallation.
|
||||
"""
|
||||
|
||||
oe_workspace = {str(k): v for k, v in (oe_workspace or {}).items()}
|
||||
oe_parent = {str(k): v for k, v in (oe_parent or {}).items()}
|
||||
geraet_oe = {str(k): v for k, v in (geraet_oe or {}).items()}
|
||||
|
||||
cur.execute(
|
||||
"SELECT id::text, parent_id::text, workspace_id::text, name FROM organisationseinheiten WHERE organization_id = %s",
|
||||
(organization_id,),
|
||||
)
|
||||
oes = {}
|
||||
for oe_id, parent_id, workspace_id, name in cur.fetchall():
|
||||
oes[oe_id] = {
|
||||
"parent": oe_parent.get(oe_id, parent_id),
|
||||
"workspace": oe_workspace.get(oe_id, workspace_id) if oe_id in oe_workspace else workspace_id,
|
||||
"name": name,
|
||||
}
|
||||
|
||||
cur.execute("SELECT w.id::text, w.name, w.backend_id, b.name FROM workspaces w LEFT JOIN backends b ON b.id = w.backend_id")
|
||||
workspaces = {wid: (name, backend_id, bname) for wid, name, backend_id, bname in cur.fetchall()}
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT d.id::text, COALESCE(d.hostname, d.id::text), d.oe_id::text, bv.backend_id, b.name
|
||||
FROM devices d
|
||||
JOIN assignments a ON a.device_id = d.id
|
||||
JOIN bereitstellungsvorlagen bv ON bv.id = a.bereitstellungsvorlage_id
|
||||
JOIN backends b ON b.id = bv.backend_id
|
||||
WHERE d.organization_id = %s AND d.archived_at IS NULL AND d.deprovisioned_at IS NULL
|
||||
""",
|
||||
(organization_id,),
|
||||
)
|
||||
konflikte = []
|
||||
for device_id, hostname, oe_id, backend_id, distro in cur.fetchall():
|
||||
aktuell, gesehen = geraet_oe.get(device_id, oe_id), set()
|
||||
while aktuell is not None and aktuell in oes and aktuell not in gesehen:
|
||||
gesehen.add(aktuell)
|
||||
ws = oes[aktuell]["workspace"]
|
||||
if ws is not None:
|
||||
ws_name, ws_backend, ws_distro = workspaces.get(str(ws), ("?", None, None))
|
||||
if ws_backend is not None and ws_backend != backend_id:
|
||||
konflikte.append({
|
||||
"geraet": hostname, "geraet_distro": distro,
|
||||
"oe": oes[aktuell]["name"], "workspace": ws_name, "workspace_distro": ws_distro,
|
||||
})
|
||||
break
|
||||
aktuell = oes[aktuell]["parent"]
|
||||
return konflikte
|
||||
|
||||
|
||||
def konflikt_meldung(konflikte: list[dict]) -> str:
|
||||
beispiele = "; ".join(
|
||||
f"{k['geraet']} ({k['geraet_distro']}) bekäme „{k['workspace']}“ ({k['workspace_distro']}) über OE {k['oe']}"
|
||||
for k in konflikte[:5]
|
||||
)
|
||||
rest = f" und {len(konflikte) - 5} weitere" if len(konflikte) > 5 else ""
|
||||
return (
|
||||
f"Nicht möglich – {len(konflikte)} Gerät(e) hätten danach einen Workspace mit anderer Distro, "
|
||||
f"das ginge nur per Neuinstallation: {beispiele}{rest}."
|
||||
)
|
||||
|
||||
|
||||
def set_oe_workspace(oe_id: str, workspace_id: str | None, version_id: str | None, automatisch: bool):
|
||||
"""Workspace (oder keinen) an eine OE hängen. Rückgabe (True, None) oder
|
||||
(None, (fehlercode, nachricht))."""
|
||||
|
||||
with get_database_connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SELECT organization_id FROM organisationseinheiten WHERE id = %s", (oe_id,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
return None, ("not_found", "OE wurde nicht gefunden.")
|
||||
organization_id = str(row[0])
|
||||
|
||||
if workspace_id is not None:
|
||||
sichtbar = {w["id"] for w in fetch_available_workspaces_for_organization(organization_id)}
|
||||
if workspace_id not in sichtbar:
|
||||
return None, ("workspace_nicht_verfuegbar", "Dieser Workspace steht der Organisation nicht zur Verfügung.")
|
||||
cur.execute("SELECT backend_id FROM workspaces WHERE id = %s", (workspace_id,))
|
||||
if cur.fetchone()[0] is None:
|
||||
return None, ("ohne_distro", "Der Workspace hat noch keine Distro und kann nicht zugewiesen werden.")
|
||||
if not automatisch:
|
||||
if not version_id:
|
||||
return None, ("version_fehlt", "Bitte eine Version wählen oder „automatisch aktualisieren“ setzen.")
|
||||
cur.execute(
|
||||
"SELECT 1 FROM workspace_versionen WHERE id = %s AND workspace_id = %s AND status = 'veroeffentlicht'",
|
||||
(version_id, workspace_id),
|
||||
)
|
||||
if cur.fetchone() is None:
|
||||
return None, ("version_ungueltig", "Die gewählte Version gehört nicht zu diesem Workspace.")
|
||||
konflikte = distro_konflikte(cur, organization_id, oe_workspace={oe_id: workspace_id})
|
||||
if konflikte:
|
||||
return None, ("distro_konflikt", konflikt_meldung(konflikte))
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE organisationseinheiten
|
||||
SET workspace_id = %s, workspace_version_id = %s, automatisch_aktualisieren = %s
|
||||
WHERE id = %s
|
||||
""",
|
||||
(
|
||||
workspace_id,
|
||||
None if (workspace_id is None or automatisch) else version_id,
|
||||
True if workspace_id is None else automatisch,
|
||||
oe_id,
|
||||
),
|
||||
)
|
||||
return True, None
|
||||
|
||||
|
||||
def fetch_gruppen(organization_id: str):
|
||||
with get_database_connection() as conn:
|
||||
@@ -2143,44 +2332,56 @@ def fetch_devices_for_organization(organization_id: str):
|
||||
cur.execute(
|
||||
f"""
|
||||
SELECT d.id, d.hostname, d.device_fingerprint, d.agent_last_checkin,
|
||||
d.deprovisioned_at, d.oe_id, oe.name, w.name, {GERAETE_HEALTH_SPALTEN_SQL}
|
||||
d.deprovisioned_at, d.oe_id, oe.name, w.name, {GERAETE_HEALTH_SPALTEN_SQL}, be.name
|
||||
FROM devices d
|
||||
LEFT JOIN organisationseinheiten oe ON oe.id = d.oe_id
|
||||
LEFT JOIN assignments a ON a.device_id = d.id
|
||||
LEFT JOIN bereitstellungsvorlagen bv ON bv.id = a.bereitstellungsvorlage_id
|
||||
LEFT JOIN workspaces w ON w.id = bv.workspace_id
|
||||
LEFT JOIN backends be ON be.id = bv.backend_id
|
||||
WHERE d.organization_id = %s AND d.archived_at IS NULL
|
||||
ORDER BY d.hostname NULLS LAST, d.created_at
|
||||
""",
|
||||
(organization_id,),
|
||||
)
|
||||
return [
|
||||
{
|
||||
"id": str(device_id),
|
||||
"hostname": hostname,
|
||||
"device_fingerprint": fingerprint,
|
||||
"agent_last_checkin": (
|
||||
last_checkin.isoformat() if last_checkin is not None else None
|
||||
),
|
||||
"deprovisioned_at": (
|
||||
deprovisioned_at.isoformat() if deprovisioned_at is not None else None
|
||||
),
|
||||
"oe_id": str(oe_id) if oe_id is not None else None,
|
||||
"oe_name": oe_name,
|
||||
# Install-time zugewiesener Workspace (GUI-Umbau
|
||||
# 10.09.2026, Geräteliste-Spalte) - bei einer nachträglichen
|
||||
# Gerätegruppen-Workspace-Übersteuerung (ADR-0028) weicht
|
||||
# der tatsächlich wirksame Katalog davon ab, für den
|
||||
# genauen Stand siehe der Auftragskatalog-Seite des Geräts.
|
||||
"workspace_name": workspace_name,
|
||||
**_health_felder_aus_row(*health_spalten),
|
||||
}
|
||||
for (
|
||||
device_id, hostname, fingerprint, last_checkin, deprovisioned_at, oe_id, oe_name,
|
||||
workspace_name,
|
||||
*health_spalten,
|
||||
) in cur.fetchall()
|
||||
]
|
||||
zeilen = cur.fetchall()
|
||||
|
||||
# Wirksamer Workspace (Schritt F): über OE oder Installation,
|
||||
# wie ihn der Agent anwendet - nicht mehr nur der der Installation.
|
||||
wirksam = {}
|
||||
for zeile in zeilen:
|
||||
basis = geraete_basis(cur, str(zeile[0]))
|
||||
if basis is not None and basis[0] is not None:
|
||||
cur.execute(
|
||||
"SELECT w.name, v.nummer FROM workspace_versionen v JOIN workspaces w ON w.id = v.workspace_id WHERE v.id = %s",
|
||||
(basis[0],),
|
||||
)
|
||||
wirksam[zeile[0]] = cur.fetchone()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": str(device_id),
|
||||
"hostname": hostname,
|
||||
"device_fingerprint": fingerprint,
|
||||
"agent_last_checkin": (
|
||||
last_checkin.isoformat() if last_checkin is not None else None
|
||||
),
|
||||
"deprovisioned_at": (
|
||||
deprovisioned_at.isoformat() if deprovisioned_at is not None else None
|
||||
),
|
||||
"oe_id": str(oe_id) if oe_id is not None else None,
|
||||
"oe_name": oe_name,
|
||||
"workspace_name": wirksam[device_id][0] if device_id in wirksam else workspace_name,
|
||||
"workspace_version": wirksam[device_id][1] if device_id in wirksam else None,
|
||||
"distro": rest[-1],
|
||||
**_health_felder_aus_row(*rest[:-1]),
|
||||
}
|
||||
for (
|
||||
device_id, hostname, fingerprint, last_checkin, deprovisioned_at, oe_id, oe_name,
|
||||
workspace_name,
|
||||
*rest,
|
||||
) in zeilen
|
||||
]
|
||||
|
||||
def fetch_all_devices():
|
||||
"""
|
||||
@@ -4160,26 +4361,71 @@ def fetch_enrollment_sessions(organization_id: str):
|
||||
) in cur.fetchall()
|
||||
]
|
||||
|
||||
def ziel_oe_pruefen(cur, organization_id: str, ziel_oe_id: str, bereitstellungsvorlage_id: str) -> str | None:
|
||||
"""Passt die Ziel-OE zum Installationsprofil? Fehlermeldung oder None.
|
||||
Gilt in der OE (auch geerbt) ein Workspace, muss das Profil dieselbe
|
||||
Distro installieren."""
|
||||
|
||||
cur.execute(
|
||||
"SELECT 1 FROM organisationseinheiten WHERE id = %s AND organization_id = %s",
|
||||
(ziel_oe_id, organization_id),
|
||||
)
|
||||
if cur.fetchone() is None:
|
||||
return "Die Ziel-OE gehört nicht zu dieser Organisation."
|
||||
cur.execute(
|
||||
"SELECT bv.backend_id, b.name FROM bereitstellungsvorlagen bv JOIN backends b ON b.id = bv.backend_id WHERE bv.id = %s",
|
||||
(bereitstellungsvorlage_id,),
|
||||
)
|
||||
profil = cur.fetchone()
|
||||
if profil is None:
|
||||
return "Das Installationsprofil wurde nicht gefunden."
|
||||
cur.execute("SELECT id, parent_id, workspace_id FROM organisationseinheiten WHERE organization_id = %s", (organization_id,))
|
||||
oes = {oe: (parent, ws) for oe, parent, ws in cur.fetchall()}
|
||||
aktuell = UUID(str(ziel_oe_id))
|
||||
while aktuell is not None and aktuell in oes:
|
||||
parent, ws = oes[aktuell]
|
||||
if ws is not None:
|
||||
cur.execute(
|
||||
"SELECT w.name, w.backend_id, b.name FROM workspaces w LEFT JOIN backends b ON b.id = w.backend_id WHERE w.id = %s",
|
||||
(ws,),
|
||||
)
|
||||
ws_name, ws_backend, ws_distro = cur.fetchone()
|
||||
if ws_backend is not None and ws_backend != profil[0]:
|
||||
return (
|
||||
f"Die Ziel-OE nutzt „{ws_name}“ ({ws_distro}), das Installationsprofil installiert aber "
|
||||
f"{profil[1]}. Bitte ein {ws_distro}-Profil oder eine andere OE wählen."
|
||||
)
|
||||
return None
|
||||
aktuell = parent
|
||||
return None
|
||||
|
||||
|
||||
def create_enrollment_session(
|
||||
organization_id: str, bereitstellungsvorlage_id: str, max_devices: int, expires_at: str,
|
||||
neuinstallation_ohne_rueckfrage: bool = False,
|
||||
neuinstallation_ohne_rueckfrage: bool = False, ziel_oe_id: str | None = None,
|
||||
):
|
||||
"""Rückgabe: ({"id": ...}, None) oder (None, fehlermeldung)."""
|
||||
|
||||
session_id = uuid4()
|
||||
|
||||
with get_database_connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if ziel_oe_id:
|
||||
fehler = ziel_oe_pruefen(cur, organization_id, ziel_oe_id, bereitstellungsvorlage_id)
|
||||
if fehler:
|
||||
return None, fehler
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO enrollment_sessions
|
||||
(id, organization_id, bereitstellungsvorlage_id, max_devices, expires_at,
|
||||
neuinstallation_ohne_rueckfrage)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
neuinstallation_ohne_rueckfrage, ziel_oe_id)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||
""",
|
||||
(session_id, organization_id, bereitstellungsvorlage_id, max_devices, expires_at,
|
||||
neuinstallation_ohne_rueckfrage),
|
||||
neuinstallation_ohne_rueckfrage, ziel_oe_id or None),
|
||||
)
|
||||
|
||||
return {"id": str(session_id)}
|
||||
return {"id": str(session_id)}, None
|
||||
|
||||
def revoke_enrollment_session(session_id: str):
|
||||
with get_database_connection() as conn:
|
||||
@@ -4638,6 +4884,8 @@ def activate(payload: ActivationRequest, request: Request):
|
||||
|
||||
if enrollment_session is not None:
|
||||
consume_enrollment_session(enrollment_session["session_id"])
|
||||
if enrollment_session.get("ziel_oe_id"):
|
||||
geraet_in_ziel_oe(device["id"], enrollment_session["ziel_oe_id"])
|
||||
|
||||
write_log({
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
@@ -4747,8 +4995,6 @@ def resolve_template(template_id: str, payload: ResolveRequest):
|
||||
disk_encryption, partitioning, secure_boot_required,
|
||||
) = template_row
|
||||
|
||||
blueprints = version_blueprints(cur, veroeffentlichte_version_id(cur, workspace_id), backend_id)
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO assignments (id, device_id, bereitstellungsvorlage_id)
|
||||
@@ -4759,6 +5005,24 @@ def resolve_template(template_id: str, payload: ResolveRequest):
|
||||
(uuid4(), payload.device_id, template_id),
|
||||
)
|
||||
|
||||
# Schritt F: dieselbe Grundausstattung, die der Agent später
|
||||
# anwendet - liegt das Gerät (z. B. über die Ziel-OE des Mediums)
|
||||
# in einer OE mit Workspace, gilt deren Workspace, sonst wie
|
||||
# bisher der Workspace des Profils.
|
||||
basis = geraete_basis(cur, payload.device_id)
|
||||
if basis is not None:
|
||||
version_id, _backend = basis
|
||||
blueprints = version_blueprints(cur, version_id, backend_id)
|
||||
cur.execute(
|
||||
"SELECT w.key FROM workspace_versionen v JOIN workspaces w ON w.id = v.workspace_id WHERE v.id = %s",
|
||||
(version_id,),
|
||||
)
|
||||
key_row = cur.fetchone()
|
||||
if key_row:
|
||||
workspace_key = key_row[0]
|
||||
else:
|
||||
blueprints = version_blueprints(cur, veroeffentlichte_version_id(cur, workspace_id), backend_id)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"runtime_blueprint": {
|
||||
@@ -5906,6 +6170,18 @@ def create_bereitstellungsvorlage_endpoint(
|
||||
elif payload.extra_partitions:
|
||||
return {"success": False, "error": "invalid_request", "message": "extra_partitions ist nur bei scheme='custom' erlaubt."}
|
||||
|
||||
with get_database_connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SELECT backend_id FROM workspaces WHERE id = %s", (payload.workspace_id,))
|
||||
ws_row = cur.fetchone()
|
||||
if ws_row is None:
|
||||
return {"success": False, "error": "workspace_not_found", "message": "Der Workspace wurde nicht gefunden."}
|
||||
if ws_row[0] is not None and str(ws_row[0]) != payload.backend_id:
|
||||
return {
|
||||
"success": False, "error": "distro_konflikt",
|
||||
"message": "Der Workspace gehört zu einer anderen Distro als das Profil (ein Workspace hat genau eine Distro).",
|
||||
}
|
||||
|
||||
vorlage = create_bereitstellungsvorlage(
|
||||
organization_id,
|
||||
payload.workspace_id,
|
||||
@@ -5948,10 +6224,12 @@ def create_enrollment_session_endpoint(
|
||||
if not require_service_token(authorization):
|
||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||
|
||||
session = create_enrollment_session(
|
||||
session, fehler = create_enrollment_session(
|
||||
organization_id, payload.bereitstellungsvorlage_id, payload.max_devices, payload.expires_at,
|
||||
payload.neuinstallation_ohne_rueckfrage,
|
||||
payload.neuinstallation_ohne_rueckfrage, payload.ziel_oe_id,
|
||||
)
|
||||
if fehler:
|
||||
return {"success": False, "error": "ziel_oe_konflikt", "message": fehler}
|
||||
|
||||
return {"success": True, "enrollment_session": session}
|
||||
|
||||
@@ -6131,6 +6409,17 @@ def create_organisationseinheit_endpoint(
|
||||
return {"success": True, "organisationseinheit": oe}
|
||||
|
||||
|
||||
@app.put("/api/v1/organisationseinheiten/{oe_id}/workspace")
|
||||
def set_oe_workspace_endpoint(oe_id: str, payload: SetOeWorkspaceRequest, authorization: str | None = Header(default=None)):
|
||||
if not require_service_token(authorization):
|
||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||
|
||||
_ok, fehler = set_oe_workspace(oe_id, payload.workspace_id, payload.version_id, payload.automatisch)
|
||||
if fehler:
|
||||
return {"success": False, "error": fehler[0], "message": fehler[1]}
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@app.patch("/api/v1/organisationseinheiten/{oe_id}")
|
||||
def update_organisationseinheit_endpoint(
|
||||
oe_id: str,
|
||||
@@ -6142,6 +6431,9 @@ def update_organisationseinheit_endpoint(
|
||||
|
||||
oe = update_organisationseinheit(oe_id, payload.name, payload.parent_id)
|
||||
|
||||
if oe is not None and "konflikt" in oe:
|
||||
return {"success": False, "error": "distro_konflikt", "message": oe["konflikt"]}
|
||||
|
||||
if oe is None:
|
||||
return {
|
||||
"success": False,
|
||||
@@ -6219,7 +6511,10 @@ def move_device_to_oe_endpoint(device_id: str, payload: MoveDeviceOeRequest, aut
|
||||
if not require_service_token(authorization):
|
||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||
|
||||
if not move_device_to_oe(device_id, payload.oe_id):
|
||||
ergebnis = move_device_to_oe(device_id, payload.oe_id)
|
||||
if isinstance(ergebnis, str):
|
||||
return {"success": False, "error": "distro_konflikt", "message": ergebnis}
|
||||
if not ergebnis:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "invalid_move",
|
||||
|
||||
Reference in new issue
Block a user