feat: expose infrastructure dependency inventory
Module Package Release / publish-packages (push) Successful in 12s
Module Package Release / publish-packages (push) Successful in 12s
This commit is contained in:
@@ -49,6 +49,18 @@ The Ops API reports:
|
|||||||
- rendered PostgreSQL connection peak, declared server limit, and operator reserve
|
- rendered PostgreSQL connection peak, declared server limit, and operator reserve
|
||||||
- recovery operation status, mode, checkpoint count, and last update
|
- recovery operation status, mode, checkpoint count, and last update
|
||||||
|
|
||||||
|
The separately authorized
|
||||||
|
`GET /api/v1/ops/infrastructure/dependencies` endpoint collects current,
|
||||||
|
module-owned infrastructure dependencies through the Core provider contract.
|
||||||
|
It is intended for the host deployer's destructive-change preflight, not for a
|
||||||
|
general data export. Results contain stable configuration references, lifecycle
|
||||||
|
states, scopes, numeric counts and required preparation actions; they never
|
||||||
|
contain credentials, tenant identifiers, file keys or secret-bearing endpoint
|
||||||
|
values. Ops itself contributes the current database, Redis coordination,
|
||||||
|
ingress, and load-balancing runtime bindings; feature modules contribute their
|
||||||
|
own persisted configuration and data. One unavailable or invalid provider makes the result incomplete, so a
|
||||||
|
caller must fail closed rather than treating missing provider data as zero.
|
||||||
|
|
||||||
These values are intentionally diagnostic. They do not replace deployment
|
These values are intentionally diagnostic. They do not replace deployment
|
||||||
configuration management, backups, monitoring, or restore drills.
|
configuration management, backups, monitoring, or restore drills.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/ops-webui",
|
"name": "@govoplan/ops-webui",
|
||||||
"version": "0.1.20",
|
"version": "0.1.21",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "webui/src/index.ts",
|
"main": "webui/src/index.ts",
|
||||||
|
|||||||
+2
-2
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-ops"
|
name = "govoplan-ops"
|
||||||
version = "0.1.20"
|
version = "0.1.21"
|
||||||
description = "GovOPlaN operations module for health, deployment profile, and sizing visibility."
|
description = "GovOPlaN operations module for health, deployment profile, and sizing visibility."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
authors = [{ name = "GovOPlaN" }]
|
authors = [{ name = "GovOPlaN" }]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"govoplan-core>=0.1.18",
|
"govoplan-core>=0.1.42",
|
||||||
"govoplan-access>=0.1.18",
|
"govoplan-access>=0.1.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
__all__ = ["__version__"]
|
__all__ = ["__version__"]
|
||||||
|
|
||||||
__version__ = "0.1.20"
|
__version__ = "0.1.21"
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ from govoplan_core.db.session import get_database
|
|||||||
from govoplan_core.settings import settings as core_settings
|
from govoplan_core.settings import settings as core_settings
|
||||||
|
|
||||||
from govoplan_ops.backend.manifest import OPS_READ_SCOPES, OPS_RUN_SCOPES
|
from govoplan_ops.backend.manifest import OPS_READ_SCOPES, OPS_RUN_SCOPES
|
||||||
from govoplan_ops.backend.infrastructure import deployment_capability_status
|
from govoplan_ops.backend.infrastructure import (
|
||||||
|
deployment_capability_status,
|
||||||
|
infrastructure_dependency_inventory,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/ops", tags=["ops"])
|
router = APIRouter(prefix="/ops", tags=["ops"])
|
||||||
_module_check_cache: dict[str, tuple[float, dict[str, Any]]] = {}
|
_module_check_cache: dict[str, tuple[float, dict[str, Any]]] = {}
|
||||||
@@ -93,6 +96,20 @@ def run_ops_checks(
|
|||||||
return _ops_status_payload(request, force_module_checks=True)
|
return _ops_status_payload(request, force_module_checks=True)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/infrastructure/dependencies")
|
||||||
|
def infrastructure_dependencies(
|
||||||
|
request: Request,
|
||||||
|
principal: ApiPrincipal = Depends(require_any_scope(*OPS_READ_SCOPES)),
|
||||||
|
) -> dict[str, object]:
|
||||||
|
"""Return a fresh, non-secret inventory for host deployment preflight."""
|
||||||
|
|
||||||
|
del principal
|
||||||
|
return infrastructure_dependency_inventory(
|
||||||
|
_registry(request),
|
||||||
|
installation_id=core_settings.installation_id,
|
||||||
|
).to_dict()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/runtime/nodes")
|
@router.get("/runtime/nodes")
|
||||||
def runtime_nodes(
|
def runtime_nodes(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|||||||
@@ -3,8 +3,96 @@ from __future__ import annotations
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from govoplan_core.core.infrastructure_capabilities import (
|
from govoplan_core.core.infrastructure_capabilities import (
|
||||||
|
INFRASTRUCTURE_DEPENDENCY_PROVIDER_CAPABILITY_PREFIX,
|
||||||
|
InfrastructureDependency,
|
||||||
|
InfrastructureDependencyInventory,
|
||||||
|
InfrastructureDependencyProvider,
|
||||||
|
collect_infrastructure_dependency_inventory,
|
||||||
deployment_capability_status as _deployment_capability_status,
|
deployment_capability_status as _deployment_capability_status,
|
||||||
|
load_infrastructure_capability_receipt,
|
||||||
)
|
)
|
||||||
|
from govoplan_core.settings import settings as core_settings
|
||||||
|
|
||||||
|
|
||||||
|
OPS_INFRASTRUCTURE_DEPENDENCY_CAPABILITY = (
|
||||||
|
f"{INFRASTRUCTURE_DEPENDENCY_PROVIDER_CAPABILITY_PREFIX}ops"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OpsInfrastructureDependencyProvider(InfrastructureDependencyProvider):
|
||||||
|
"""Report Core runtime bindings that optional feature providers do not own."""
|
||||||
|
|
||||||
|
module_id = "ops"
|
||||||
|
capability_ids = (
|
||||||
|
"database.postgresql",
|
||||||
|
"coordination.redis",
|
||||||
|
"network.ingress",
|
||||||
|
"runtime.load_balancing",
|
||||||
|
)
|
||||||
|
|
||||||
|
def infrastructure_dependencies(self) -> tuple[InfrastructureDependency, ...]:
|
||||||
|
dependencies = [
|
||||||
|
InfrastructureDependency(
|
||||||
|
capability_id="database.postgresql",
|
||||||
|
module_id=self.module_id,
|
||||||
|
dependency_type="application_state_binding",
|
||||||
|
dependency_ref="runtime:database.postgresql",
|
||||||
|
state="runtime_binding",
|
||||||
|
scope="system",
|
||||||
|
summary=(
|
||||||
|
"GovOPlaN persists application and control-plane state in the active PostgreSQL database."
|
||||||
|
),
|
||||||
|
metrics={},
|
||||||
|
required_action=(
|
||||||
|
"Create and restore-verify a coordinated database backup before rebinding PostgreSQL."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InfrastructureDependency(
|
||||||
|
capability_id="network.ingress",
|
||||||
|
module_id=self.module_id,
|
||||||
|
dependency_type="public_runtime_binding",
|
||||||
|
dependency_ref="runtime:network.ingress",
|
||||||
|
state="runtime_binding",
|
||||||
|
scope="system",
|
||||||
|
summary="Ops and application APIs are reached through the active ingress boundary.",
|
||||||
|
metrics={},
|
||||||
|
required_action=(
|
||||||
|
"Verify trusted proxy headers, TLS, health, and public reachability on the replacement ingress."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InfrastructureDependency(
|
||||||
|
capability_id="runtime.load_balancing",
|
||||||
|
module_id=self.module_id,
|
||||||
|
dependency_type="runtime_routing_binding",
|
||||||
|
dependency_ref="runtime:load-balancing",
|
||||||
|
state="runtime_binding",
|
||||||
|
scope="system",
|
||||||
|
summary="API and WebUI replicas are reached through the runtime load balancer.",
|
||||||
|
metrics={},
|
||||||
|
required_action=(
|
||||||
|
"Verify health-aware API and WebUI routing before replacing the load balancer."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
if core_settings.redis_url.strip():
|
||||||
|
dependencies.append(
|
||||||
|
InfrastructureDependency(
|
||||||
|
capability_id="coordination.redis",
|
||||||
|
module_id=self.module_id,
|
||||||
|
dependency_type="coordination_runtime_binding",
|
||||||
|
dependency_ref="runtime:coordination.redis",
|
||||||
|
state="runtime_binding",
|
||||||
|
scope="system",
|
||||||
|
summary=(
|
||||||
|
"The active runtime uses Redis for queues or shared coordination."
|
||||||
|
),
|
||||||
|
metrics={"celery_enabled": int(core_settings.celery_enabled)},
|
||||||
|
required_action=(
|
||||||
|
"Drain queued and in-flight work, verify idempotent recovery, and provision replacement coordination before rebinding or disabling Redis."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return tuple(dependencies)
|
||||||
|
|
||||||
|
|
||||||
def deployment_capability_status(
|
def deployment_capability_status(
|
||||||
@@ -15,4 +103,31 @@ def deployment_capability_status(
|
|||||||
return _deployment_capability_status(path)
|
return _deployment_capability_status(path)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["deployment_capability_status"]
|
def infrastructure_dependency_inventory(
|
||||||
|
registry: object,
|
||||||
|
*,
|
||||||
|
installation_id: str,
|
||||||
|
) -> InfrastructureDependencyInventory:
|
||||||
|
"""Collect module-owned persisted dependencies for an authorized host plan."""
|
||||||
|
|
||||||
|
receipt = load_infrastructure_capability_receipt()
|
||||||
|
if receipt is None:
|
||||||
|
raise ValueError(
|
||||||
|
"Infrastructure dependency inventory requires a mounted deployment receipt."
|
||||||
|
)
|
||||||
|
if receipt.installation_id != installation_id:
|
||||||
|
raise ValueError(
|
||||||
|
"Mounted deployment receipt belongs to a different installation."
|
||||||
|
)
|
||||||
|
return collect_infrastructure_dependency_inventory(
|
||||||
|
registry,
|
||||||
|
installation_id=installation_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"deployment_capability_status",
|
||||||
|
"infrastructure_dependency_inventory",
|
||||||
|
"OPS_INFRASTRUCTURE_DEPENDENCY_CAPABILITY",
|
||||||
|
"OpsInfrastructureDependencyProvider",
|
||||||
|
]
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from govoplan_core.core.modules import with_documentation_structured_translations
|
from govoplan_core.core.modules import with_documentation_structured_translations
|
||||||
from govoplan_ops.backend.german_structured_documentation import GERMAN_STRUCTURED_TRANSLATIONS
|
from govoplan_ops.backend.german_structured_documentation import GERMAN_STRUCTURED_TRANSLATIONS
|
||||||
|
from govoplan_ops.backend.infrastructure import (
|
||||||
|
OPS_INFRASTRUCTURE_DEPENDENCY_CAPABILITY,
|
||||||
|
OpsInfrastructureDependencyProvider,
|
||||||
|
)
|
||||||
|
|
||||||
from govoplan_core.core.access import (
|
from govoplan_core.core.access import (
|
||||||
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
||||||
@@ -9,6 +13,7 @@ from govoplan_core.core.access import (
|
|||||||
)
|
)
|
||||||
from govoplan_core.core.modules import (
|
from govoplan_core.core.modules import (
|
||||||
DocumentationCondition,
|
DocumentationCondition,
|
||||||
|
DocumentationLink,
|
||||||
DocumentationTopic,
|
DocumentationTopic,
|
||||||
FrontendModule,
|
FrontendModule,
|
||||||
FrontendRoute,
|
FrontendRoute,
|
||||||
@@ -95,7 +100,7 @@ def _route_factory(context: ModuleContext):
|
|||||||
manifest = ModuleManifest(
|
manifest = ModuleManifest(
|
||||||
id="ops",
|
id="ops",
|
||||||
name="Ops",
|
name="Ops",
|
||||||
version="0.1.20",
|
version="0.1.21",
|
||||||
required_capabilities=(
|
required_capabilities=(
|
||||||
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
||||||
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
||||||
@@ -137,15 +142,32 @@ manifest = ModuleManifest(
|
|||||||
cache_seconds=15,
|
cache_seconds=15,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
capability_factories={
|
||||||
|
OPS_INFRASTRUCTURE_DEPENDENCY_CAPABILITY: (
|
||||||
|
lambda context: OpsInfrastructureDependencyProvider()
|
||||||
|
),
|
||||||
|
},
|
||||||
documentation=(
|
documentation=(
|
||||||
DocumentationTopic(
|
DocumentationTopic(
|
||||||
id="ops.health-governance-and-sizing",
|
id="ops.health-governance-and-sizing",
|
||||||
title="Inspect platform health and deployment posture",
|
title="Inspect platform health and deployment posture",
|
||||||
summary="Ops combines module-owned health checks with deployment profile, governance inventory, worker assumptions, and sizing guidance.",
|
summary="Ops combines module-owned health checks with deployment profile, governance inventory, worker assumptions, and sizing guidance.",
|
||||||
body="Read-only status distinguishes configured capabilities from healthy integrations. Worker and queue providers use a Core runtime-status contract, so Ops never imports a provider backend. The surface distinguishes intentionally disabled, unconfigured, starting, healthy with unsupported queue depth, measured idle, busy, degraded, stale, and unreachable states. It shows enabled/configured state, backend, workers, heartbeat age and stale threshold, queue depth, active/reserved work, and failures only when each value is actually reported; unavailable values are never rendered as zero or healthy. Local development treats intentionally disabled workers as expected, while production profiles require an enabled, configured, reachable provider before queue-backed work is accepted. Polling is bounded to one request, pauses while the page is hidden, and refreshes on return. When the deployment mounts a signed or locally generated non-secret infrastructure capability receipt, Ops shows whether PostgreSQL, Redis, SMTP, file storage, load balancing, and ingress are configured, externally supplied, available but unconfigured, or unavailable. Secret values never cross this boundary; only stable environment or credential-envelope references may be disclosed. Pending post-install tasks remain visible with a stable resume key. Authorized operators can run bounded probes; a probe must not perform unbounded business work or silently repair data. Use readiness and worker results when diagnosing a node, and use the deployment profile and sizing assumptions when planning horizontal capacity.",
|
body="Read-only status distinguishes configured capabilities from healthy integrations. Worker and queue providers use a Core runtime-status contract, so Ops never imports a provider backend. The surface distinguishes intentionally disabled, unconfigured, starting, healthy with unsupported queue depth, measured idle, busy, degraded, stale, and unreachable states. It shows enabled/configured state, backend, workers, heartbeat age and stale threshold, queue depth, active/reserved work, and failures only when each value is actually reported; unavailable values are never rendered as zero or healthy. Local development treats intentionally disabled workers as expected, while production profiles require an enabled, configured, reachable provider before queue-backed work is accepted. Polling is bounded to one request, pauses while the page is hidden, and refreshes on return. When the deployment mounts a signed or locally generated non-secret infrastructure capability receipt, Ops shows whether PostgreSQL, Redis, SMTP, file storage, load balancing, and ingress are configured, externally supplied, available but unconfigured, or unavailable. Secret values never cross this boundary; only stable environment or credential-envelope references may be disclosed. Pending post-install tasks remain visible with a stable resume key. The separately authorized infrastructure-dependency endpoint asks enabled module providers for their current persisted configuration and data dependencies. Each provider returns only stable references, states, scopes, numeric metrics, and required migration actions; one failed provider marks the entire fresh inventory incomplete so the host deployer blocks a capability change. Authorized operators can run bounded probes; a probe must not perform unbounded business work or silently repair data. Use readiness and worker results when diagnosing a node, and use the deployment profile and sizing assumptions when planning horizontal capacity.",
|
||||||
documentation_types=("admin", "user"),
|
documentation_types=("admin", "user"),
|
||||||
audience=("operator", "system_admin"),
|
audience=("operator", "system_admin"),
|
||||||
related_modules=("audit", "docs", "notifications"),
|
related_modules=("audit", "docs", "notifications"),
|
||||||
|
links=(
|
||||||
|
DocumentationLink(
|
||||||
|
label="Infrastructure dependency inventory API",
|
||||||
|
href="/api/v1/ops/infrastructure/dependencies",
|
||||||
|
kind="api",
|
||||||
|
),
|
||||||
|
DocumentationLink(
|
||||||
|
label="Operations profile handbook",
|
||||||
|
href="govoplan-ops/docs/SCALABILITY_PROFILES.md",
|
||||||
|
kind="repository",
|
||||||
|
),
|
||||||
|
),
|
||||||
translations={
|
translations={
|
||||||
"de": {
|
"de": {
|
||||||
"title": "Plattformzustand und Bereitstellungsprofil prüfen",
|
"title": "Plattformzustand und Bereitstellungsprofil prüfen",
|
||||||
@@ -159,7 +181,9 @@ manifest = ModuleManifest(
|
|||||||
"Die Abfrage bleibt auf eine Anfrage je Intervall begrenzt, pausiert bei ausgeblendeter Seite und wird bei der Rückkehr fortgesetzt. "
|
"Die Abfrage bleibt auf eine Anfrage je Intervall begrenzt, pausiert bei ausgeblendeter Seite und wird bei der Rückkehr fortgesetzt. "
|
||||||
"Ist ein signierter oder lokal erzeugter Infrastrukturbeleg ohne Geheimwerte eingebunden, zeigt Ops für PostgreSQL, Redis, SMTP, Dateispeicher, Lastverteilung und Ingress, ob die Fähigkeit konfiguriert, extern bereitgestellt, verfügbar aber nicht konfiguriert oder nicht verfügbar ist. "
|
"Ist ein signierter oder lokal erzeugter Infrastrukturbeleg ohne Geheimwerte eingebunden, zeigt Ops für PostgreSQL, Redis, SMTP, Dateispeicher, Lastverteilung und Ingress, ob die Fähigkeit konfiguriert, extern bereitgestellt, verfügbar aber nicht konfiguriert oder nicht verfügbar ist. "
|
||||||
"Geheimwerte überschreiten diese Grenze nie; offengelegt werden dürfen nur stabile Umgebungs- oder Credential-Envelope-Referenzen. "
|
"Geheimwerte überschreiten diese Grenze nie; offengelegt werden dürfen nur stabile Umgebungs- oder Credential-Envelope-Referenzen. "
|
||||||
"Ausstehende Aufgaben nach einer Installation bleiben mit einem stabilen Fortsetzungsschlüssel sichtbar. Autorisierte Betriebsverantwortliche dürfen begrenzte Prüfungen ausführen; eine Prüfung darf weder unbegrenzte Facharbeit auslösen noch Daten stillschweigend reparieren. "
|
"Ausstehende Aufgaben nach einer Installation bleiben mit einem stabilen Fortsetzungsschlüssel sichtbar. Der separat autorisierte Infrastruktur-Abhängigkeitsendpunkt fragt aktivierte Modulprovider nach ihren aktuellen gespeicherten Konfigurations- und Datenabhängigkeiten. "
|
||||||
|
"Jeder Provider liefert nur stabile Referenzen, Zustände, Geltungsbereiche, numerische Kennzahlen und erforderliche Migrationsmaßnahmen; schlägt ein Provider fehl, ist das gesamte frische Inventar unvollständig und der Host-Deployer blockiert die Fähigkeitsänderung. "
|
||||||
|
"Autorisierte Betriebsverantwortliche dürfen begrenzte Prüfungen ausführen; eine Prüfung darf weder unbegrenzte Facharbeit auslösen noch Daten stillschweigend reparieren. "
|
||||||
"Nutzen Sie Bereitschafts- und Worker-Ergebnisse zur Diagnose eines Knotens sowie Bereitstellungsprofil und Dimensionierungsannahmen zur Planung horizontaler Kapazität."
|
"Nutzen Sie Bereitschafts- und Worker-Ergebnisse zur Diagnose eines Knotens sowie Bereitstellungsprofil und Dimensionierungsannahmen zur Planung horizontaler Kapazität."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,73 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from types import SimpleNamespace
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from govoplan_ops.backend.infrastructure import deployment_capability_status
|
from govoplan_core.core.infrastructure_capabilities import InfrastructureDependency
|
||||||
|
from govoplan_ops.backend.infrastructure import (
|
||||||
|
OpsInfrastructureDependencyProvider,
|
||||||
|
deployment_capability_status,
|
||||||
|
infrastructure_dependency_inventory,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _Provider:
|
||||||
|
module_id = "mail"
|
||||||
|
capability_ids = ("mail.smtp",)
|
||||||
|
|
||||||
|
def infrastructure_dependencies(self) -> tuple[InfrastructureDependency, ...]:
|
||||||
|
return (
|
||||||
|
InfrastructureDependency(
|
||||||
|
capability_id="mail.smtp",
|
||||||
|
module_id="mail",
|
||||||
|
dependency_type="smtp_endpoint",
|
||||||
|
dependency_ref="mail:server-1",
|
||||||
|
state="active",
|
||||||
|
scope="system",
|
||||||
|
summary="Persisted SMTP endpoint.",
|
||||||
|
metrics={"credential_binding_count": 1},
|
||||||
|
required_action="Rebind it before changing SMTP.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _Registry:
|
||||||
|
def capability_names(self) -> tuple[str, ...]:
|
||||||
|
return ("infrastructure.dependency_inventory.mail",)
|
||||||
|
|
||||||
|
def capability(self, name: str) -> object | None:
|
||||||
|
return _Provider() if name.endswith(".mail") else None
|
||||||
|
|
||||||
|
|
||||||
class InfrastructureCapabilityTests(unittest.TestCase):
|
class InfrastructureCapabilityTests(unittest.TestCase):
|
||||||
|
def test_collects_authorized_module_dependency_inventory(self) -> None:
|
||||||
|
with patch(
|
||||||
|
"govoplan_ops.backend.infrastructure.load_infrastructure_capability_receipt",
|
||||||
|
return_value=SimpleNamespace(installation_id="govoplan-test"),
|
||||||
|
):
|
||||||
|
result = infrastructure_dependency_inventory(
|
||||||
|
_Registry(),
|
||||||
|
installation_id="govoplan-test",
|
||||||
|
).to_dict()
|
||||||
|
|
||||||
|
self.assertTrue(result["complete"])
|
||||||
|
self.assertEqual("mail.smtp", result["dependencies"][0]["capability_id"])
|
||||||
|
self.assertEqual(1, result["dependencies"][0]["metrics"]["credential_binding_count"])
|
||||||
|
|
||||||
|
def test_ops_provider_reports_runtime_bindings_without_endpoint_secrets(self) -> None:
|
||||||
|
dependencies = OpsInfrastructureDependencyProvider().infrastructure_dependencies()
|
||||||
|
by_capability = {item.capability_id: item for item in dependencies}
|
||||||
|
|
||||||
|
self.assertIn("database.postgresql", by_capability)
|
||||||
|
self.assertIn("network.ingress", by_capability)
|
||||||
|
self.assertEqual(
|
||||||
|
"runtime:database.postgresql",
|
||||||
|
by_capability["database.postgresql"].dependency_ref,
|
||||||
|
)
|
||||||
|
self.assertNotIn("postgresql://", json.dumps([item.to_dict() for item in dependencies]))
|
||||||
|
|
||||||
def test_reads_bounded_non_secret_capability_receipt(self) -> None:
|
def test_reads_bounded_non_secret_capability_receipt(self) -> None:
|
||||||
with tempfile.TemporaryDirectory(prefix="govoplan-ops-capabilities-") as root:
|
with tempfile.TemporaryDirectory(prefix="govoplan-ops-capabilities-") as root:
|
||||||
path = Path(root) / "capabilities.json"
|
path = Path(root) / "capabilities.json"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/ops-webui",
|
"name": "@govoplan/ops-webui",
|
||||||
"version": "0.1.20",
|
"version": "0.1.21",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user