feat(device-plane): add canonical Device Manager runtime
This commit is contained in:
@@ -38,6 +38,30 @@ DOCKER = Path("/usr/local/bin/docker")
|
||||
MAP_GATEWAY_SECRET_DIR = Path("/volume1/docker/nodedc-platform/secrets")
|
||||
MAP_GATEWAY_SECRET_FILE = MAP_GATEWAY_SECRET_DIR / "map-gateway-admin-secret"
|
||||
MAP_EGRESS_PROXY_SECRET_FILE = MAP_GATEWAY_SECRET_DIR / "map-egress-proxy-token"
|
||||
PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE = (
|
||||
MAP_GATEWAY_SECRET_DIR / "device-core-internal-token"
|
||||
)
|
||||
PLATFORM_DEVICE_CORE_HUB_TRUST_REL = (
|
||||
"platform/deployment/device-core-hub-trust-v1.json"
|
||||
)
|
||||
PLATFORM_DEVICE_CORE_HUB_TRUST_ENTRIES = (
|
||||
"platform/docker-compose.platform-http.yml",
|
||||
PLATFORM_DEVICE_CORE_HUB_TRUST_REL,
|
||||
)
|
||||
PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_REL = (
|
||||
"platform/deployment/device-manager-public-route-v1.json"
|
||||
)
|
||||
PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_ENTRIES = (
|
||||
"platform/Caddyfile.http",
|
||||
PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_REL,
|
||||
)
|
||||
LAUNCHER_DEVICE_CORE_SESSION_ENTRIES = (
|
||||
"server/control-plane-store.mjs",
|
||||
"server/dev-server.mjs",
|
||||
"server/device-core-session-access.mjs",
|
||||
"server/internal-request-auth.mjs",
|
||||
"src/shared/api/adminApi.ts",
|
||||
)
|
||||
PROXY_CONTUR_ENV_FILE = Path("/volume1/docker/proxy-contur/.env")
|
||||
DC_AMD_PROXY_RUNTIME_DIR = Path("/volume1/docker/dc-amd-proxy/runtime")
|
||||
DEVICE_PLANE_ROOT = Path("/volume1/docker/nodedc-device-plane")
|
||||
@@ -45,8 +69,12 @@ DEVICE_PLANE_SECRET_DIR = DEVICE_PLANE_ROOT / "secrets"
|
||||
DEVICE_PLANE_POSTGRES_PASSWORD_FILE = DEVICE_PLANE_SECRET_DIR / "postgres-password"
|
||||
DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE = DEVICE_PLANE_SECRET_DIR / "gateway-core-token"
|
||||
DEVICE_PLANE_IDENTIFIER_PEPPER_FILE = DEVICE_PLANE_SECRET_DIR / "identifier-pepper"
|
||||
DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE = (
|
||||
DEVICE_PLANE_SECRET_DIR / "management-core-token"
|
||||
)
|
||||
DEVICE_PLANE_CONTROL_CORE_IMAGE = "nodedc/device-control-core:local"
|
||||
DEVICE_PLANE_GATEWAY_IMAGE = "nodedc/device-gateway:local"
|
||||
DEVICE_PLANE_MANAGER_IMAGE = "nodedc/device-manager:local"
|
||||
DEVICE_PLANE_BACKHAUL_TARGET_IMAGE = "nodedc/device-backhaul-target:local"
|
||||
DEVICE_PLANE_BACKHAUL_FAILED_TARGET_REL = (
|
||||
"deployment/device-plane-backhaul-target-v1.json"
|
||||
@@ -182,6 +210,26 @@ DEVICE_PLANE_FOUNDATION_ENTRIES = (
|
||||
"services/device-control-core",
|
||||
"services/device-gateway",
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL = (
|
||||
"deployment/device-manager-control-plane-v1.json"
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml"
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_SHA256 = (
|
||||
"4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e"
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES = (
|
||||
".dockerignore",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_REL,
|
||||
"packages/device-protocol-contract",
|
||||
"packages/arusnavi-b2-adapter",
|
||||
"services/device-control-core",
|
||||
"services/device-gateway/package.json",
|
||||
"services/device-edge-relay/package.json",
|
||||
"services/device-manager",
|
||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
||||
)
|
||||
DEVICE_PLANE_FOUNDATION_RECOVERY_REL = (
|
||||
"deployment/device-plane-foundation-recovery-v1.json"
|
||||
)
|
||||
@@ -1478,8 +1526,9 @@ COMPONENTS = {
|
||||
),
|
||||
"bootstrap_root": True,
|
||||
"compose_no_deps": True,
|
||||
# PostgreSQL is durable state infrastructure. Normal application
|
||||
# overlays can rebuild/recreate only these two stateless services.
|
||||
# PostgreSQL is durable state infrastructure. The default legacy
|
||||
# selection remains Core + Gateway; the exact Device Manager slice
|
||||
# separately registers the third stateless service.
|
||||
"services": ("device-control-core", "device-gateway"),
|
||||
},
|
||||
"proxy-contur": {
|
||||
@@ -2641,9 +2690,11 @@ def denied_payload_path(component, rel):
|
||||
pass
|
||||
elif component == "device-plane" and rel in (
|
||||
"docker-compose.device-plane.yml",
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_REL,
|
||||
DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL,
|
||||
"services/device-control-core/Dockerfile",
|
||||
"services/device-gateway/Dockerfile",
|
||||
"services/device-manager/Dockerfile",
|
||||
"services/device-backhaul-target/Dockerfile",
|
||||
):
|
||||
pass
|
||||
@@ -2791,8 +2842,10 @@ def denied_payload_path(component, rel):
|
||||
return "device-plane test path"
|
||||
if rel.startswith((
|
||||
"docker-compose.device-plane.yml.bak",
|
||||
"docker-compose.device-manager.yml.bak",
|
||||
"services/device-control-core/Dockerfile.bak",
|
||||
"services/device-gateway/Dockerfile.bak",
|
||||
"services/device-manager/Dockerfile.bak",
|
||||
)):
|
||||
return "device-plane backup file"
|
||||
elif component == "n8n-private-extension":
|
||||
@@ -2916,6 +2969,8 @@ def allowed_payload_path(component, rel):
|
||||
"platform/Caddyfile.http",
|
||||
"platform/docker-compose.platform-http.yml",
|
||||
"platform/docker-compose.external-data-plane.yml",
|
||||
PLATFORM_DEVICE_CORE_HUB_TRUST_REL,
|
||||
PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_REL,
|
||||
):
|
||||
return True
|
||||
if rel == "platform/notification-core" or rel.startswith("platform/notification-core/"):
|
||||
@@ -3051,6 +3106,7 @@ def allowed_payload_path(component, rel):
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"docker-compose.device-plane.yml",
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_REL,
|
||||
DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL,
|
||||
DEVICE_PLANE_POSTGRES_BOOTSTRAP_REL,
|
||||
DEVICE_PLANE_FOUNDATION_RECOVERY_REL,
|
||||
@@ -3058,11 +3114,13 @@ def allowed_payload_path(component, rel):
|
||||
DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL,
|
||||
DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL,
|
||||
DEVICE_PLANE_BACKHAUL_TARGET_REL,
|
||||
DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
||||
"packages/device-protocol-contract",
|
||||
"packages/arusnavi-b2-adapter",
|
||||
"services/device-control-core",
|
||||
"services/device-gateway",
|
||||
"services/device-edge-relay/package.json",
|
||||
"services/device-manager",
|
||||
"services/device-backhaul-target",
|
||||
):
|
||||
return True
|
||||
@@ -3071,6 +3129,7 @@ def allowed_payload_path(component, rel):
|
||||
"packages/arusnavi-b2-adapter/",
|
||||
"services/device-control-core/",
|
||||
"services/device-gateway/",
|
||||
"services/device-manager/",
|
||||
"services/device-backhaul-target/",
|
||||
)):
|
||||
return True
|
||||
@@ -8086,6 +8145,21 @@ def load_artifact(artifact, work_dir):
|
||||
die(f"files.txt entry missing in payload: {rel}")
|
||||
|
||||
validate_payload_tree(manifest["component"], payload_dir, entries)
|
||||
if is_platform_device_core_hub_trust_slice(
|
||||
manifest["component"],
|
||||
entries,
|
||||
):
|
||||
validate_platform_device_core_hub_trust_payload(payload_dir)
|
||||
if is_platform_device_manager_public_route_slice(
|
||||
manifest["component"],
|
||||
entries,
|
||||
):
|
||||
validate_platform_device_manager_public_route_payload(payload_dir)
|
||||
if is_device_plane_manager_control_plane_slice(
|
||||
manifest["component"],
|
||||
entries,
|
||||
):
|
||||
validate_device_plane_manager_control_plane_payload(payload_dir)
|
||||
if is_device_plane_postgres_bootstrap_slice(
|
||||
manifest["component"],
|
||||
entries,
|
||||
@@ -8449,6 +8523,156 @@ def component_root(component):
|
||||
return COMPONENTS[component]["payload_root"]
|
||||
|
||||
|
||||
def is_platform_device_core_hub_trust_slice(component, entries):
|
||||
return (
|
||||
component == "platform"
|
||||
and entries is not None
|
||||
and tuple(entries) == PLATFORM_DEVICE_CORE_HUB_TRUST_ENTRIES
|
||||
)
|
||||
|
||||
|
||||
def is_platform_device_manager_public_route_slice(component, entries):
|
||||
return (
|
||||
component == "platform"
|
||||
and entries is not None
|
||||
and tuple(entries) == PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_ENTRIES
|
||||
)
|
||||
|
||||
|
||||
def is_launcher_device_core_session_slice(component, entries):
|
||||
return (
|
||||
component == "launcher"
|
||||
and entries is not None
|
||||
and tuple(entries) == LAUNCHER_DEVICE_CORE_SESSION_ENTRIES
|
||||
)
|
||||
|
||||
|
||||
def is_device_plane_manager_control_plane_slice(component, entries):
|
||||
return (
|
||||
component == "device-plane"
|
||||
and entries is not None
|
||||
and tuple(entries) == DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES
|
||||
)
|
||||
|
||||
|
||||
def expected_platform_device_core_hub_trust_descriptor():
|
||||
return {
|
||||
"schemaVersion": "nodedc.platform.device-core-hub-trust.v1",
|
||||
"action": "activate",
|
||||
"serviceSlug": "device-core",
|
||||
"launcherCredential": "runner-managed-file",
|
||||
"credentialScope": ["handoff.consume", "session.validate"],
|
||||
"publicRoute": "unchanged",
|
||||
}
|
||||
|
||||
|
||||
def expected_platform_device_manager_public_route_descriptor():
|
||||
return {
|
||||
"schemaVersion": "nodedc.platform.device-manager-public-route.v1",
|
||||
"action": "activate",
|
||||
"hostname": "device.nodedc.ru",
|
||||
"upstream": "device-manager:18122",
|
||||
"transport": "reverse-proxy",
|
||||
"rawTcpIngress": "forbidden",
|
||||
}
|
||||
|
||||
|
||||
def expected_device_plane_manager_control_plane_descriptor():
|
||||
return {
|
||||
"schemaVersion": (
|
||||
"nodedc.device-plane.device-manager-control-plane.v1"
|
||||
),
|
||||
"action": "activate",
|
||||
"service": "device-manager",
|
||||
"publicIngress": "reverse-proxy-only",
|
||||
"deviceCoreManagementApi": "file-token-authenticated",
|
||||
"launcherTrust": "file-token-scoped-to-device-core-handoff",
|
||||
"commandTransport": "disabled",
|
||||
"gelios": "untouched",
|
||||
}
|
||||
|
||||
|
||||
def validate_platform_device_core_hub_trust_payload(payload_dir):
|
||||
descriptor = read_strict_json(
|
||||
payload_dir / PLATFORM_DEVICE_CORE_HUB_TRUST_REL,
|
||||
"Platform Device Core Hub trust descriptor",
|
||||
max_bytes=16 * 1024,
|
||||
)
|
||||
if descriptor != expected_platform_device_core_hub_trust_descriptor():
|
||||
die("Platform Device Core Hub trust descriptor mismatch")
|
||||
compose = (
|
||||
payload_dir / "platform/docker-compose.platform-http.yml"
|
||||
).read_text(encoding="utf-8")
|
||||
for required in (
|
||||
"NODEDC_DEVICE_CORE_INTERNAL_TOKEN_FILE: "
|
||||
"/run/nodedc-secrets/device-core-internal-token",
|
||||
"source: /volume1/docker/nodedc-platform/secrets/"
|
||||
"device-core-internal-token",
|
||||
"create_host_path: false",
|
||||
):
|
||||
if required not in compose:
|
||||
die(f"Platform Device Core Hub trust boundary missing: {required}")
|
||||
return descriptor
|
||||
|
||||
|
||||
def validate_platform_device_manager_public_route_payload(payload_dir):
|
||||
descriptor = read_strict_json(
|
||||
payload_dir / PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_REL,
|
||||
"Platform Device Manager public route descriptor",
|
||||
max_bytes=16 * 1024,
|
||||
)
|
||||
if descriptor != expected_platform_device_manager_public_route_descriptor():
|
||||
die("Platform Device Manager public route descriptor mismatch")
|
||||
caddy = (payload_dir / "platform/Caddyfile.http").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
for required in (
|
||||
"http://device.nodedc.ru",
|
||||
"reverse_proxy device-manager:18122",
|
||||
"header_up X-Forwarded-Proto https",
|
||||
):
|
||||
if required not in caddy:
|
||||
die(f"Platform Device Manager route boundary missing: {required}")
|
||||
return descriptor
|
||||
|
||||
|
||||
def validate_device_plane_manager_control_plane_payload(payload_dir):
|
||||
descriptor = read_strict_json(
|
||||
payload_dir / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
|
||||
"Device Manager control-plane descriptor",
|
||||
max_bytes=16 * 1024,
|
||||
)
|
||||
if descriptor != expected_device_plane_manager_control_plane_descriptor():
|
||||
die("Device Manager control-plane descriptor mismatch")
|
||||
compose_path = payload_dir / DEVICE_PLANE_MANAGER_COMPOSE_REL
|
||||
if sha256_file(compose_path) != DEVICE_PLANE_MANAGER_COMPOSE_SHA256:
|
||||
die("Device Manager control-plane Compose mismatch")
|
||||
compose = compose_path.read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
for required in (
|
||||
"device-manager:",
|
||||
'DEVICE_MANAGEMENT_API_ENABLED: "true"',
|
||||
"DEVICE_MANAGEMENT_CORE_TOKEN_FILE: "
|
||||
"/run/nodedc-secrets/management-core-token",
|
||||
"NODEDC_LAUNCHER_INTERNAL_TOKEN_FILE: "
|
||||
"/run/nodedc-secrets/device-core-internal-token",
|
||||
"NODEDC_DEVICE_CORE_TOKEN_FILE: "
|
||||
"/run/nodedc-secrets/management-core-token",
|
||||
"name: nodedc-platform_edge",
|
||||
):
|
||||
if required not in compose:
|
||||
die(f"Device Manager control-plane boundary missing: {required}")
|
||||
for forbidden in (
|
||||
"NODEDC_INTERNAL_ACCESS_TOKEN:",
|
||||
"NODEDC_PLATFORM_SERVICE_TOKEN:",
|
||||
"0.0.0.0:18122",
|
||||
):
|
||||
if forbidden in compose:
|
||||
die(f"Device Manager control-plane boundary violation: {forbidden}")
|
||||
return descriptor
|
||||
|
||||
|
||||
def is_device_plane_postgres_bootstrap_slice(component, entries):
|
||||
return (
|
||||
component == "device-plane"
|
||||
@@ -9674,6 +9898,7 @@ def validate_device_plane_b2_discovery_rollback_recovery_evidence(
|
||||
def device_plane_service_container_ids(service):
|
||||
if service not in (
|
||||
*DEVICE_PLANE_RUNTIME_SERVICES,
|
||||
"device-manager",
|
||||
DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,
|
||||
):
|
||||
die(f"Device Plane runtime service is not registered: {service}")
|
||||
@@ -11044,7 +11269,7 @@ def validate_device_plane_backhaul_target_runtime(
|
||||
}
|
||||
|
||||
|
||||
def validate_device_plane_runtime_secret_metadata():
|
||||
def validate_device_plane_runtime_secret_metadata(include_management=False):
|
||||
try:
|
||||
directory_stat = DEVICE_PLANE_SECRET_DIR.lstat()
|
||||
except FileNotFoundError:
|
||||
@@ -11057,11 +11282,17 @@ def validate_device_plane_runtime_secret_metadata():
|
||||
or stat.S_IMODE(directory_stat.st_mode) != 0o710
|
||||
):
|
||||
die("Device Plane runtime secret directory boundary mismatch")
|
||||
for path, label in (
|
||||
required_secrets = [
|
||||
(DEVICE_PLANE_POSTGRES_PASSWORD_FILE, "PostgreSQL"),
|
||||
(DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE, "Gateway to Core"),
|
||||
(DEVICE_PLANE_IDENTIFIER_PEPPER_FILE, "identifier pepper"),
|
||||
):
|
||||
]
|
||||
if include_management:
|
||||
required_secrets.append((
|
||||
DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE,
|
||||
"management to Core",
|
||||
))
|
||||
for path, label in required_secrets:
|
||||
try:
|
||||
path_stat = path.lstat()
|
||||
except FileNotFoundError:
|
||||
@@ -11084,6 +11315,111 @@ def validate_device_plane_runtime_secret_metadata():
|
||||
return "exact"
|
||||
|
||||
|
||||
def validate_device_manager_control_plane_runtime():
|
||||
core_ids = device_plane_service_container_ids("device-control-core")
|
||||
manager_ids = device_plane_service_container_ids("device-manager")
|
||||
if len(core_ids) != 1 or len(manager_ids) != 1:
|
||||
die("Device Manager control-plane runtime topology mismatch")
|
||||
core = inspect_device_plane_container(core_ids[0])
|
||||
manager = inspect_device_plane_container(manager_ids[0])
|
||||
core_environment = container_environment(core, "Device Control Core")
|
||||
manager_environment = container_environment(manager, "Device Manager")
|
||||
if (
|
||||
core_environment.get("DEVICE_MANAGEMENT_API_ENABLED") != "true"
|
||||
or core_environment.get("DEVICE_MANAGEMENT_CORE_TOKEN_FILE")
|
||||
!= "/run/nodedc-secrets/management-core-token"
|
||||
):
|
||||
die("Device Control Core management runtime mismatch")
|
||||
expected_manager_environment = {
|
||||
"NODE_ENV": "production",
|
||||
"HOST": "0.0.0.0",
|
||||
"PORT": "18122",
|
||||
"NODEDC_DEVICE_MANAGER_AUTH_REQUIRED": "true",
|
||||
"NODEDC_DEVICE_MANAGER_COOKIE_SECURE": "true",
|
||||
"NODEDC_DEVICE_MANAGER_LOCAL_PREVIEW": "false",
|
||||
"NODEDC_DEVICE_MANAGER_SERVICE_SLUG": "device-core",
|
||||
"NODEDC_LAUNCHER_BASE_URL": "https://hub.nodedc.ru",
|
||||
"NODEDC_LAUNCHER_INTERNAL_URL": "http://launcher:5173",
|
||||
"NODEDC_LAUNCHER_INTERNAL_TOKEN_FILE": (
|
||||
"/run/nodedc-secrets/device-core-internal-token"
|
||||
),
|
||||
"NODEDC_DEVICE_CORE_INTERNAL_URL": (
|
||||
"http://device-control-core:18120"
|
||||
),
|
||||
"NODEDC_DEVICE_CORE_TOKEN_FILE": (
|
||||
"/run/nodedc-secrets/management-core-token"
|
||||
),
|
||||
}
|
||||
if any(
|
||||
manager_environment.get(key) != value
|
||||
for key, value in expected_manager_environment.items()
|
||||
):
|
||||
die("Device Manager environment mismatch")
|
||||
for environment in (core_environment, manager_environment):
|
||||
for forbidden in (
|
||||
"NODEDC_INTERNAL_ACCESS_TOKEN",
|
||||
"NODEDC_PLATFORM_SERVICE_TOKEN",
|
||||
"DEVICE_MANAGEMENT_CORE_TOKEN",
|
||||
):
|
||||
if forbidden in environment:
|
||||
die("Device Manager plaintext secret boundary mismatch")
|
||||
core_mounts = {
|
||||
mount.get("Destination"): mount
|
||||
for mount in core.get("Mounts") or []
|
||||
}
|
||||
core_management_mount = core_mounts.get(
|
||||
"/run/nodedc-secrets/management-core-token"
|
||||
)
|
||||
if (
|
||||
core_management_mount is None
|
||||
or core_management_mount.get("Type") != "bind"
|
||||
or core_management_mount.get("Source")
|
||||
!= str(DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE)
|
||||
or core_management_mount.get("RW") is not False
|
||||
):
|
||||
die("Device Control Core management secret mount mismatch")
|
||||
manager_mounts = {
|
||||
mount.get("Destination"): mount
|
||||
for mount in manager.get("Mounts") or []
|
||||
}
|
||||
expected_manager_mounts = {
|
||||
"/run/nodedc-secrets/device-core-internal-token": (
|
||||
PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE
|
||||
),
|
||||
"/run/nodedc-secrets/management-core-token": (
|
||||
DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE
|
||||
),
|
||||
}
|
||||
if set(manager_mounts) != set(expected_manager_mounts):
|
||||
die("Device Manager mount set mismatch")
|
||||
for destination, source in expected_manager_mounts.items():
|
||||
mount = manager_mounts[destination]
|
||||
if (
|
||||
mount.get("Type") != "bind"
|
||||
or mount.get("Source") != str(source)
|
||||
or mount.get("RW") is not False
|
||||
):
|
||||
die("Device Manager secret mount mismatch")
|
||||
ports = (manager.get("NetworkSettings") or {}).get("Ports") or {}
|
||||
if any(bindings for bindings in ports.values()):
|
||||
die("Device Manager host port publication is forbidden")
|
||||
networks = set(
|
||||
((manager.get("NetworkSettings") or {}).get("Networks") or {}).keys()
|
||||
)
|
||||
if networks != {
|
||||
DEVICE_PLANE_PRIVATE_NETWORK,
|
||||
"nodedc-platform_edge",
|
||||
}:
|
||||
die("Device Manager network boundary mismatch")
|
||||
validate_device_plane_runtime_secret_metadata(include_management=True)
|
||||
ensure_platform_runtime_secret(
|
||||
PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE,
|
||||
MAP_GATEWAY_SECRET_RE,
|
||||
"Device Core Hub handoff",
|
||||
)
|
||||
return "exact"
|
||||
|
||||
|
||||
def validate_device_plane_foundation_installed_source():
|
||||
failed_artifact = FAILED_DIR / DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT
|
||||
with tempfile.TemporaryDirectory(
|
||||
@@ -12804,6 +13140,9 @@ def component_services(component, entries=None):
|
||||
# authorization normalization inside the already-active backend.
|
||||
return ("nodedc-backend",)
|
||||
|
||||
if is_device_plane_manager_control_plane_slice(component, entries):
|
||||
return ("device-control-core", "device-manager")
|
||||
|
||||
if component == "device-plane" and entries is not None:
|
||||
selected = []
|
||||
|
||||
@@ -12882,6 +13221,12 @@ def component_services(component, entries=None):
|
||||
selected.append("proxy")
|
||||
return tuple(selected)
|
||||
|
||||
if is_platform_device_core_hub_trust_slice(component, entries):
|
||||
return ("launcher",)
|
||||
|
||||
if is_platform_device_manager_public_route_slice(component, entries):
|
||||
return ("reverse-proxy",)
|
||||
|
||||
if component == "platform" and entries is not None:
|
||||
if is_platform_provider_catalog_only(entries):
|
||||
return ()
|
||||
@@ -13045,6 +13390,16 @@ def component_compose_files(
|
||||
return tuple(files)
|
||||
files = COMPONENTS[component].get("compose_files", ())
|
||||
if component == "device-plane":
|
||||
manager_overlay = DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_COMPOSE_REL
|
||||
if manager_overlay.exists() or manager_overlay.is_symlink():
|
||||
if (
|
||||
manager_overlay.is_symlink()
|
||||
or not manager_overlay.is_file()
|
||||
or sha256_file(manager_overlay)
|
||||
!= DEVICE_PLANE_MANAGER_COMPOSE_SHA256
|
||||
):
|
||||
die("installed Device Manager Compose drift detected")
|
||||
files = (*files, manager_overlay)
|
||||
overlay = DEVICE_PLANE_ROOT / DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL
|
||||
if overlay.exists() or overlay.is_symlink():
|
||||
if (
|
||||
@@ -13088,6 +13443,39 @@ def component_build_args(component, entries=None):
|
||||
|
||||
|
||||
def component_builds(component, entries=None):
|
||||
if is_platform_device_core_hub_trust_slice(component, entries):
|
||||
return ()
|
||||
|
||||
if is_platform_device_manager_public_route_slice(component, entries):
|
||||
return ()
|
||||
|
||||
if is_device_plane_manager_control_plane_slice(component, entries):
|
||||
return (
|
||||
(
|
||||
DEVICE_PLANE_ROOT,
|
||||
(
|
||||
"build",
|
||||
"--no-cache",
|
||||
"--network=host",
|
||||
"-f",
|
||||
"services/device-control-core/Dockerfile",
|
||||
"-t",
|
||||
DEVICE_PLANE_CONTROL_CORE_IMAGE,
|
||||
".",
|
||||
),
|
||||
),
|
||||
(
|
||||
DEVICE_PLANE_ROOT / "services/device-manager",
|
||||
(
|
||||
"build",
|
||||
"--no-cache",
|
||||
"-t",
|
||||
DEVICE_PLANE_MANAGER_IMAGE,
|
||||
".",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
if is_device_plane_backhaul_vps_enrollment_slice(component, entries):
|
||||
return ()
|
||||
|
||||
@@ -13155,6 +13543,17 @@ def component_builds(component, entries=None):
|
||||
".",
|
||||
),
|
||||
))
|
||||
if "device-manager" in selected_services:
|
||||
builds.append((
|
||||
DEVICE_PLANE_ROOT / "services/device-manager",
|
||||
(
|
||||
"build",
|
||||
"--no-cache",
|
||||
"-t",
|
||||
DEVICE_PLANE_MANAGER_IMAGE,
|
||||
".",
|
||||
),
|
||||
))
|
||||
return tuple(builds)
|
||||
|
||||
if component == "platform" and entries is not None:
|
||||
@@ -16694,6 +17093,15 @@ def plan_artifact(artifact):
|
||||
print(f"runtime_secret=runner-managed:{DEVICE_PLANE_POSTGRES_PASSWORD_FILE}")
|
||||
print(f"runtime_secret=runner-managed:{DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE}")
|
||||
print(f"runtime_secret=runner-managed:{DEVICE_PLANE_IDENTIFIER_PEPPER_FILE}")
|
||||
if is_device_plane_manager_control_plane_slice(component, entries):
|
||||
print(
|
||||
"runtime_secret=runner-managed:"
|
||||
f"{DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE}"
|
||||
)
|
||||
print(
|
||||
"runtime_secret=runner-managed:"
|
||||
f"{PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE}"
|
||||
)
|
||||
print(
|
||||
"device_postgres="
|
||||
f"{device_plane_postgres_plan_selection(device_plane_postgres_preflight)}"
|
||||
@@ -16977,6 +17385,19 @@ def plan_artifact(artifact):
|
||||
)
|
||||
print("device_postgres_bootstrap_mode=create-if-absent")
|
||||
print("device_postgres_rollback_volume=preserve")
|
||||
if is_platform_device_core_hub_trust_slice(component, entries):
|
||||
print(
|
||||
"runtime_secret=runner-managed:"
|
||||
f"{PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE}"
|
||||
)
|
||||
print("device_core_hub_credential_scope=handoff+session-only")
|
||||
print("device_manager_public_route=unchanged")
|
||||
if is_platform_device_manager_public_route_slice(component, entries):
|
||||
print("device_manager_public_route=https-via-reverse-proxy")
|
||||
print("device_manager_raw_tcp_ingress=forbidden")
|
||||
if is_launcher_device_core_session_slice(component, entries):
|
||||
print("device_core_owner_scopes=hub-signed-runtime-claims")
|
||||
print("device_core_company_scope=active-admin-grant-only")
|
||||
if touches_external_data_plane:
|
||||
print(f"runtime_secret=runner-managed:{EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE}")
|
||||
print(f"runtime_grants=runner-managed:{EXTERNAL_DATA_PLANE_READER_GRANTS_DIR}")
|
||||
@@ -18227,6 +18648,17 @@ def prepare_component_runtime(component, entries=None):
|
||||
MAP_GATEWAY_SECRET_RE,
|
||||
"device plane identifier pepper",
|
||||
)
|
||||
if is_device_plane_manager_control_plane_slice(component, entries):
|
||||
ensure_platform_runtime_secret(
|
||||
DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE,
|
||||
MAP_GATEWAY_SECRET_RE,
|
||||
"device plane management to Core",
|
||||
)
|
||||
ensure_platform_runtime_secret(
|
||||
PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE,
|
||||
MAP_GATEWAY_SECRET_RE,
|
||||
"Device Core Hub handoff",
|
||||
)
|
||||
if is_device_plane_backhaul_vps_enrollment_slice(
|
||||
component,
|
||||
entries,
|
||||
@@ -18272,6 +18704,12 @@ def prepare_component_runtime(component, entries=None):
|
||||
return
|
||||
|
||||
if component == "platform" and entries is not None:
|
||||
if is_platform_device_core_hub_trust_slice(component, entries):
|
||||
ensure_platform_runtime_secret(
|
||||
PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE,
|
||||
MAP_GATEWAY_SECRET_RE,
|
||||
"Device Core Hub handoff",
|
||||
)
|
||||
touches_map_gateway = any(rel == "platform/services/map-gateway" or rel.startswith("platform/services/map-gateway/") for rel in entries)
|
||||
touches_external_data_plane = touches_external_data_plane_files(entries)
|
||||
if touches_map_gateway:
|
||||
@@ -18483,6 +18921,44 @@ def module_foundry_healthcheck():
|
||||
|
||||
|
||||
def component_healthchecks(component, entries=None, services=None):
|
||||
if is_platform_device_core_hub_trust_slice(component, entries):
|
||||
return ({
|
||||
"url": "http://127.0.0.1:18080/healthz",
|
||||
"headers": {"Host": "hub.nodedc.ru"},
|
||||
},)
|
||||
if is_platform_device_manager_public_route_slice(component, entries):
|
||||
return ({
|
||||
"url": "http://127.0.0.1:18080/healthz",
|
||||
"headers": {"Host": "device.nodedc.ru"},
|
||||
"expected_json": {
|
||||
"ok": True,
|
||||
"service": "nodedc-device-manager",
|
||||
"authRequired": True,
|
||||
"deviceCoreConfigured": True,
|
||||
},
|
||||
},)
|
||||
if is_launcher_device_core_session_slice(component, entries):
|
||||
return ({
|
||||
"url": "http://127.0.0.1:18080/healthz",
|
||||
"headers": {"Host": "hub.nodedc.ru"},
|
||||
"expected_json": {
|
||||
"ok": True,
|
||||
"service": "nodedc-launcher-bff",
|
||||
"deviceCoreInternalAccessConfigured": True,
|
||||
},
|
||||
},)
|
||||
if is_device_plane_manager_control_plane_slice(component, entries):
|
||||
return ({
|
||||
"url": "http://127.0.0.1:18120/healthz",
|
||||
"expected_json": {
|
||||
"ok": True,
|
||||
"service": "nodedc-device-control-core",
|
||||
"database": "ready",
|
||||
"discoveryIngest": "enabled",
|
||||
"managementApi": "enabled",
|
||||
"commandTransport": "disabled",
|
||||
},
|
||||
},)
|
||||
if is_device_plane_b2_discovery_rollback_recovery_slice(
|
||||
component,
|
||||
entries,
|
||||
@@ -19100,6 +19576,18 @@ def run_healthchecks(component, entries=None, services=None):
|
||||
healthcheck_url(check)
|
||||
assert_loopback_tcp_port_open(9921)
|
||||
return
|
||||
if is_device_plane_manager_control_plane_slice(component, entries):
|
||||
if tuple(services or ()) != (
|
||||
"device-control-core",
|
||||
"device-manager",
|
||||
):
|
||||
die("Device Manager control-plane service set mismatch")
|
||||
for service in services:
|
||||
healthcheck_compose_service("device-plane", service)
|
||||
for check in component_healthchecks(component, entries, services):
|
||||
healthcheck_url(check)
|
||||
validate_device_manager_control_plane_runtime()
|
||||
return
|
||||
if component == "platform" and entries is not None and is_platform_provider_catalog_only(entries):
|
||||
return
|
||||
if is_engine_l2_closed_loop_slice(component, entries):
|
||||
|
||||
Reference in New Issue
Block a user