feat(device-core): ship host telemetry and scalable VPS inventory
This commit is contained in:
@@ -16,14 +16,16 @@ const [
|
||||
predecessorSha256,
|
||||
...extra
|
||||
] = process.argv.slice(2);
|
||||
const isV4 = patchId.startsWith("device-control-core-release-v4-");
|
||||
if (
|
||||
extra.length
|
||||
|| !/^device-control-core-release-[A-Za-z0-9._-]{1,67}$/.test(patchId)
|
||||
|| !/^device-control-core-release(?:-v[234])?-[A-Za-z0-9._-]{1,67}$/.test(patchId)
|
||||
|| (isV4 && patchId !== "device-control-core-release-v4-20260823-047")
|
||||
|| ((predecessorPatchId === undefined) !== (predecessorSha256 === undefined))
|
||||
|| (
|
||||
predecessorPatchId !== undefined
|
||||
&& (
|
||||
!/^device-control-core-release-[A-Za-z0-9._-]{1,67}$/.test(predecessorPatchId)
|
||||
!/^(?:device-control-core-release(?:-v[234])?-[A-Za-z0-9._-]{1,67}|device-control-core-migration-replay-checkpoint-recovery-20260822-046)$/.test(predecessorPatchId)
|
||||
|| predecessorPatchId === patchId
|
||||
|| !/^[0-9a-f]{64}$/.test(predecessorSha256)
|
||||
)
|
||||
@@ -37,12 +39,13 @@ if (
|
||||
|
||||
const isV3 = patchId.startsWith("device-control-core-release-v3-");
|
||||
const isV2 = patchId.startsWith("device-control-core-release-v2-");
|
||||
const includesTelemetry = isV3 || isV4;
|
||||
const coreDockerfile = await readFile(
|
||||
resolve(devicePlaneRoot, "services/device-control-core/Dockerfile"),
|
||||
"utf8",
|
||||
);
|
||||
if (
|
||||
!isV3
|
||||
!includesTelemetry
|
||||
&& coreDockerfile.includes(
|
||||
"COPY packages/infrastructure-telemetry-contract ./packages/infrastructure-telemetry-contract",
|
||||
)
|
||||
@@ -53,7 +56,23 @@ const expectedV2Predecessor = Object.freeze({
|
||||
patchId: predecessorPatchId ?? "device-control-core-release-20260812-024",
|
||||
artifactSha256: predecessorSha256 ?? "a289e909283109642e6bba3d9822a31f63423cfe0bbcd52705979681bd2bc793",
|
||||
});
|
||||
const descriptorPath = isV3
|
||||
const expectedV4Predecessor = Object.freeze({
|
||||
patchId: "device-control-core-migration-replay-checkpoint-recovery-20260822-046",
|
||||
artifactSha256: "46000c76977fb583fc7c9cf74ecf624efd8b404f7b8d0322e0270e7b8ac6e450",
|
||||
});
|
||||
if (
|
||||
isV4
|
||||
&& predecessorPatchId !== undefined
|
||||
&& (
|
||||
predecessorPatchId !== expectedV4Predecessor.patchId
|
||||
|| predecessorSha256 !== expectedV4Predecessor.artifactSha256
|
||||
)
|
||||
) {
|
||||
throw new Error("device_control_core_release_v4_predecessor_mismatch");
|
||||
}
|
||||
const descriptorPath = isV4
|
||||
? "deployment/device-control-core-release-v4.json"
|
||||
: isV3
|
||||
? "deployment/device-control-core-release-v3.json"
|
||||
: isV2
|
||||
? "deployment/device-control-core-release-v2.json"
|
||||
@@ -64,7 +83,7 @@ const entries = [
|
||||
"package-lock.json",
|
||||
"packages/device-protocol-contract",
|
||||
"packages/device-edge-channel-contract",
|
||||
...(isV3 ? ["packages/infrastructure-telemetry-contract"] : []),
|
||||
...(includesTelemetry ? ["packages/infrastructure-telemetry-contract"] : []),
|
||||
"services/device-control-core",
|
||||
descriptorPath,
|
||||
];
|
||||
@@ -83,7 +102,9 @@ try {
|
||||
descriptor.releaseId = patchId;
|
||||
if (predecessorPatchId !== undefined) {
|
||||
descriptor.predecessor = {
|
||||
kind: "release",
|
||||
kind: isV4
|
||||
? "migration-replay-checkpoint-recovery"
|
||||
: "release",
|
||||
patchId: predecessorPatchId,
|
||||
artifactSha256: predecessorSha256,
|
||||
};
|
||||
@@ -104,7 +125,7 @@ try {
|
||||
"services/device-control-core/src/device-gateway-core-runtime.mjs",
|
||||
"packages/device-protocol-contract/src/index.mjs",
|
||||
"packages/device-edge-channel-contract/src/index.mjs",
|
||||
...(isV3 ? ["packages/infrastructure-telemetry-contract/src/index.mjs"] : []),
|
||||
...(includesTelemetry ? ["packages/infrastructure-telemetry-contract/src/index.mjs"] : []),
|
||||
]) {
|
||||
const imported = spawnSync(
|
||||
process.execPath,
|
||||
@@ -118,7 +139,7 @@ try {
|
||||
|
||||
const descriptor = JSON.parse(await readFile(join(payload, descriptorPath), "utf8"));
|
||||
if (
|
||||
descriptor.schemaVersion !== `nodedc.device-plane.device-control-core-release.${isV3 ? "v3" : isV2 ? "v2" : "v1"}`
|
||||
descriptor.schemaVersion !== `nodedc.device-plane.device-control-core-release.${isV4 ? "v4" : isV3 ? "v3" : isV2 ? "v2" : "v1"}`
|
||||
|| descriptor.releaseId !== patchId
|
||||
|| descriptor.action !== "upgrade"
|
||||
|| descriptor.service !== "device-control-core"
|
||||
@@ -130,20 +151,20 @@ try {
|
||||
|| JSON.stringify(descriptor.coreNetworks) !== JSON.stringify(["device-plane-private", "device-plane-egress"])
|
||||
|| descriptor.publicIngress !== "none-on-synology"
|
||||
|| descriptor.edgeRegistrations !== "preserved"
|
||||
|| descriptor.commandTransport !== ((isV2 || isV3) ? "typed-service-ping-v1" : "disabled")
|
||||
|| descriptor.gelios !== ((isV2 || isV3) ? "untouched-legacy-only" : "untouched")
|
||||
|| descriptor.commandTransport !== ((isV2 || isV3 || isV4) ? "typed-service-ping-v1" : "disabled")
|
||||
|| descriptor.gelios !== ((isV2 || isV3 || isV4) ? "untouched-legacy-only" : "untouched")
|
||||
|| descriptor.rollback !== "restore-preapply-source-and-core-runtime"
|
||||
|| (
|
||||
(isV2 || isV3)
|
||||
(isV2 || isV3 || isV4)
|
||||
&& (
|
||||
descriptor.commandCatalog !== "allowlisted-adapter-typed-commands-only"
|
||||
|| descriptor.credentialBoundary !== "transient-core-memory-then-single-pinned-mtls-command-envelope-to-edge-never-persisted-never-logged-never-returned"
|
||||
|| descriptor.predecessor?.patchId !== expectedV2Predecessor.patchId
|
||||
|| descriptor.predecessor?.artifactSha256 !== expectedV2Predecessor.artifactSha256
|
||||
|| descriptor.predecessor?.patchId !== (isV4 ? expectedV4Predecessor : expectedV2Predecessor).patchId
|
||||
|| descriptor.predecessor?.artifactSha256 !== (isV4 ? expectedV4Predecessor : expectedV2Predecessor).artifactSha256
|
||||
)
|
||||
)
|
||||
|| (
|
||||
isV3
|
||||
includesTelemetry
|
||||
&& (
|
||||
descriptor.telemetryTransport !== "edge-channel-host-telemetry-observed-v1"
|
||||
|| descriptor.telemetryContract !== "nodedc.infrastructure.host-telemetry.v1"
|
||||
@@ -152,6 +173,17 @@ try {
|
||||
|| descriptor.telemetryFreshness !== "fifteen-seconds-missing-stale-not-unhealthy"
|
||||
)
|
||||
)
|
||||
|| (
|
||||
isV4
|
||||
&& (
|
||||
descriptor.predecessor?.kind !== "migration-replay-checkpoint-recovery"
|
||||
|| descriptor.recoveryPredecessor !== "terminal-applied-046-exact-source-runtime-database"
|
||||
|| descriptor.databasePreflight !== "final-migration-016-validated-and-host-telemetry-table-absent"
|
||||
|| descriptor.databaseRowMutation !== "none-before-core-startup-migrations"
|
||||
|| descriptor.databaseSchemaOutcome !== "migration-017-host-telemetry-table-present"
|
||||
|| descriptor.runtimePredecessor !== "healthy-recovery-046-core-generation"
|
||||
)
|
||||
)
|
||||
) {
|
||||
throw new Error("device_control_core_release_contract_mismatch");
|
||||
}
|
||||
|
||||
@@ -11,11 +11,19 @@ const platformRoot = resolve(scriptDir, "../..");
|
||||
const devicePlaneRoot = platformRoot;
|
||||
const managerRoot = resolve(platformRoot, "apps/device-manager");
|
||||
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
|
||||
const [patchId = "device-manager-release-v9-20260822-041", ...extra] = process.argv.slice(2);
|
||||
const [patchId = "device-manager-release-v13-20260823-051", ...extra] = process.argv.slice(2);
|
||||
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) throw new Error("usage: build-device-manager-control-plane-artifact.mjs [patch-id]");
|
||||
|
||||
const descriptorPath = patchId.startsWith("device-manager-release-v9-")
|
||||
? "deployment/device-manager-release-v9.json"
|
||||
const descriptorPath = patchId.startsWith("device-manager-release-v13-")
|
||||
? "deployment/device-manager-release-v13.json"
|
||||
: patchId.startsWith("device-manager-release-v12-")
|
||||
? "deployment/device-manager-release-v12.json"
|
||||
: patchId.startsWith("device-manager-release-v11-")
|
||||
? "deployment/device-manager-release-v11.json"
|
||||
: patchId.startsWith("device-manager-release-v10-")
|
||||
? "deployment/device-manager-release-v10.json"
|
||||
: patchId.startsWith("device-manager-release-v9-")
|
||||
? "deployment/device-manager-release-v9.json"
|
||||
: patchId.startsWith("device-manager-release-v8-")
|
||||
? "deployment/device-manager-release-v8.json"
|
||||
: patchId.startsWith("device-manager-release-v7-")
|
||||
@@ -37,7 +45,11 @@ const isV6 = descriptorPath.endsWith("release-v6.json");
|
||||
const isV7 = descriptorPath.endsWith("release-v7.json");
|
||||
const isV8 = descriptorPath.endsWith("release-v8.json");
|
||||
const isV9 = descriptorPath.endsWith("release-v9.json");
|
||||
const isPersistent = isV4 || isV5 || isV6 || isV7 || isV8 || isV9;
|
||||
const isV10 = descriptorPath.endsWith("release-v10.json");
|
||||
const isV11 = descriptorPath.endsWith("release-v11.json");
|
||||
const isV12 = descriptorPath.endsWith("release-v12.json");
|
||||
const isV13 = descriptorPath.endsWith("release-v13.json");
|
||||
const isPersistent = isV4 || isV5 || isV6 || isV7 || isV8 || isV9 || isV10 || isV11 || isV12 || isV13;
|
||||
const isManagerOnly = isV3 || isPersistent;
|
||||
const composeSource = resolve(devicePlaneRoot, "docker-compose.device-manager.yml");
|
||||
const composeSourceSha256 = createHash("sha256").update(await readFile(composeSource)).digest("hex");
|
||||
@@ -176,7 +188,127 @@ try {
|
||||
: "restore-preapply-snapshot")
|
||||
);
|
||||
if (commonContractInvalid) throw new Error("device_manager_activation_successor_contract_mismatch");
|
||||
if (descriptorPath.endsWith("release-v9.json")) {
|
||||
if (descriptorPath.endsWith("release-v13.json")) {
|
||||
if (
|
||||
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v13"
|
||||
|| descriptor.predecessor?.kind !== "release"
|
||||
|| descriptor.predecessor?.patchId !== "device-manager-release-v12-20260823-050"
|
||||
|| descriptor.predecessor?.artifactSha256 !== "1a49839140e5f2e49763d78f24ee47d946e244bcfde15a9c38266e8bd14c0d49"
|
||||
|| descriptor.controlCorePredecessor?.patchId !== "device-control-core-release-v4-20260823-047"
|
||||
|| descriptor.controlCorePredecessor?.artifactSha256 !== "4aecceeb8d400fdd3dbec8fc86b151691f389d165e72677f396f8994823b6b5c"
|
||||
|| descriptor.edgeChannelPredecessor?.patchId !== "device-edge-core-channel-upgrade-v4-20260812-023"
|
||||
|| descriptor.edgeChannelPredecessor?.artifactSha256 !== "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|
||||
|| descriptor.designSystem !== "nodedc-canonical-components-and-tokens-v1"
|
||||
|| descriptor.missionCoreReference !== "compute-modules-workspace-71c8b04"
|
||||
|| descriptor.infrastructureWorkspaceLayout !== "mission-core-system-workspace-v2"
|
||||
|| descriptor.hostInventoryComposition !== "mission-core-compute-host-accordion-v2"
|
||||
|| descriptor.hostInventoryOverviewSurface !== "separate-summary-soft-surface-v1"
|
||||
|| descriptor.hostInventoryCollectionSurface !== "separate-host-collection-soft-surface-v1"
|
||||
|| descriptor.hostInventoryRow !== "compact-centered-accordion-v1"
|
||||
|| descriptor.hostInventoryFreshness !== "dot-only-v1"
|
||||
|| descriptor.hostInventoryRelations !== "host-scoped-endpoint-deployment-service-v1"
|
||||
|| descriptor.hostInventoryDefaultExpansion !== "collapsed"
|
||||
|| descriptor.hostInventoryScaleTarget !== "five-hundred-collapsed-rows-v1"
|
||||
|| descriptor.telemetryWorkspace !== "mission-core-compute-module-adaptive-window-v3"
|
||||
|| descriptor.telemetrySurface !== "borderless-soft-surface-v1"
|
||||
|| descriptor.telemetryStatus !== "mission-core-dot-status-v1"
|
||||
|| descriptor.telemetryNavigation !== "full-workspace-back-navigation-v1"
|
||||
|| descriptor.telemetryScroll !== "reset-on-workspace-transition-v1"
|
||||
|| descriptor.telemetryPollInterval !== "three-seconds"
|
||||
|| descriptor.telemetryFreshness !== "fifteen-seconds-missing-stale-not-unhealthy"
|
||||
|| descriptor.telemetryOntologyProjection !== "observation-observed-property-provenance-freshness-v1"
|
||||
|| descriptor.telemetryAgent !== "telegraf-host-observer-v1"
|
||||
|| descriptor.telemetryGraphScale !== "adaptive-observed-window-explicit-domain-v1"
|
||||
|| descriptor.telemetryCpuMinimumSpan !== "five-percentage-points"
|
||||
|| descriptor.telemetryMemoryMinimumSpan !== "four-percentage-points"
|
||||
|| descriptor.telemetryNetworkMissingSemantics !== "missing-counters-never-zero-v1"
|
||||
|| descriptor.interactiveShell !== "disabled-pending-managed-session-boundary"
|
||||
|| descriptor.rollback !== "restore-preapply-snapshot-preserve-manager-data"
|
||||
|| descriptor.gelios !== "untouched-legacy-only"
|
||||
) throw new Error("device_manager_v13_host_inventory_accordion_contract_mismatch");
|
||||
await validateFaviconBundle(payload, "device_manager_v13");
|
||||
} else if (descriptorPath.endsWith("release-v12.json")) {
|
||||
if (
|
||||
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v12"
|
||||
|| descriptor.predecessor?.kind !== "release"
|
||||
|| descriptor.predecessor?.patchId !== "device-manager-release-v11-20260823-049"
|
||||
|| descriptor.predecessor?.artifactSha256 !== "c1e2056b50bfbb0d03d077461d0c27cc56cc52967c3f5620be14871c8a6d5cf0"
|
||||
|| descriptor.controlCorePredecessor?.patchId !== "device-control-core-release-v4-20260823-047"
|
||||
|| descriptor.controlCorePredecessor?.artifactSha256 !== "4aecceeb8d400fdd3dbec8fc86b151691f389d165e72677f396f8994823b6b5c"
|
||||
|| descriptor.edgeChannelPredecessor?.patchId !== "device-edge-core-channel-upgrade-v4-20260812-023"
|
||||
|| descriptor.edgeChannelPredecessor?.artifactSha256 !== "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|
||||
|| descriptor.designSystem !== "nodedc-canonical-components-and-tokens-v1"
|
||||
|| descriptor.missionCoreReference !== "compute-modules-workspace-71c8b04"
|
||||
|| descriptor.infrastructureWorkspaceLayout !== "mission-core-system-workspace-v1"
|
||||
|| descriptor.hostInventoryComposition !== "mission-core-compute-host-list-v1"
|
||||
|| descriptor.telemetryWorkspace !== "mission-core-compute-module-adaptive-window-v3"
|
||||
|| descriptor.telemetrySurface !== "borderless-soft-surface-v1"
|
||||
|| descriptor.telemetryStatus !== "mission-core-dot-status-v1"
|
||||
|| descriptor.telemetryNavigation !== "full-workspace-back-navigation-v1"
|
||||
|| descriptor.telemetryScroll !== "reset-on-workspace-transition-v1"
|
||||
|| descriptor.telemetryPollInterval !== "three-seconds"
|
||||
|| descriptor.telemetryFreshness !== "fifteen-seconds-missing-stale-not-unhealthy"
|
||||
|| descriptor.telemetryOntologyProjection !== "observation-observed-property-provenance-freshness-v1"
|
||||
|| descriptor.telemetryAgent !== "telegraf-host-observer-v1"
|
||||
|| descriptor.telemetryGraphScale !== "adaptive-observed-window-explicit-domain-v1"
|
||||
|| descriptor.telemetryCpuMinimumSpan !== "five-percentage-points"
|
||||
|| descriptor.telemetryMemoryMinimumSpan !== "four-percentage-points"
|
||||
|| descriptor.telemetryNetworkMissingSemantics !== "missing-counters-never-zero-v1"
|
||||
|| descriptor.interactiveShell !== "disabled-pending-managed-session-boundary"
|
||||
|| descriptor.rollback !== "restore-preapply-snapshot-preserve-manager-data"
|
||||
|| descriptor.gelios !== "untouched-legacy-only"
|
||||
) throw new Error("device_manager_v12_adaptive_telemetry_graph_contract_mismatch");
|
||||
await validateFaviconBundle(payload, "device_manager_v12");
|
||||
} else if (descriptorPath.endsWith("release-v11.json")) {
|
||||
if (
|
||||
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v11"
|
||||
|| descriptor.predecessor?.kind !== "release"
|
||||
|| descriptor.predecessor?.patchId !== "device-manager-release-v10-20260823-048"
|
||||
|| descriptor.predecessor?.artifactSha256 !== "e6b983a314db4f8c27d89062dfedf5ed0523cc30421170799d181a19e2d85d4c"
|
||||
|| descriptor.controlCorePredecessor?.patchId !== "device-control-core-release-v4-20260823-047"
|
||||
|| descriptor.controlCorePredecessor?.artifactSha256 !== "4aecceeb8d400fdd3dbec8fc86b151691f389d165e72677f396f8994823b6b5c"
|
||||
|| descriptor.edgeChannelPredecessor?.patchId !== "device-edge-core-channel-upgrade-v4-20260812-023"
|
||||
|| descriptor.edgeChannelPredecessor?.artifactSha256 !== "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|
||||
|| descriptor.designSystem !== "nodedc-canonical-components-and-tokens-v1"
|
||||
|| descriptor.missionCoreReference !== "compute-modules-workspace-71c8b04"
|
||||
|| descriptor.infrastructureWorkspaceLayout !== "mission-core-system-workspace-v1"
|
||||
|| descriptor.hostInventoryComposition !== "mission-core-compute-host-list-v1"
|
||||
|| descriptor.telemetryWorkspace !== "mission-core-compute-module-visual-parity-v2"
|
||||
|| descriptor.telemetrySurface !== "borderless-soft-surface-v1"
|
||||
|| descriptor.telemetryStatus !== "mission-core-dot-status-v1"
|
||||
|| descriptor.telemetryNavigation !== "full-workspace-back-navigation-v1"
|
||||
|| descriptor.telemetryScroll !== "reset-on-workspace-transition-v1"
|
||||
|| descriptor.telemetryPollInterval !== "three-seconds"
|
||||
|| descriptor.telemetryFreshness !== "fifteen-seconds-missing-stale-not-unhealthy"
|
||||
|| descriptor.telemetryOntologyProjection !== "observation-observed-property-provenance-freshness-v1"
|
||||
|| descriptor.telemetryAgent !== "telegraf-host-observer-v1"
|
||||
|| descriptor.interactiveShell !== "disabled-pending-managed-session-boundary"
|
||||
|| descriptor.rollback !== "restore-preapply-snapshot-preserve-manager-data"
|
||||
|| descriptor.gelios !== "untouched-legacy-only"
|
||||
) throw new Error("device_manager_v11_mission_core_visual_parity_contract_mismatch");
|
||||
await validateFaviconBundle(payload, "device_manager_v11");
|
||||
} else if (descriptorPath.endsWith("release-v10.json")) {
|
||||
if (
|
||||
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v10"
|
||||
|| descriptor.predecessor?.kind !== "release"
|
||||
|| descriptor.predecessor?.patchId !== "device-manager-release-v8-20260822-039"
|
||||
|| descriptor.predecessor?.artifactSha256 !== "30a83d4c6b5c029c96c4af19fa558e0ae75e4b60bc897881457304bd17e0e465"
|
||||
|| descriptor.controlCorePredecessor?.patchId !== "device-control-core-release-v4-20260823-047"
|
||||
|| descriptor.controlCorePredecessor?.artifactSha256 !== "4aecceeb8d400fdd3dbec8fc86b151691f389d165e72677f396f8994823b6b5c"
|
||||
|| descriptor.edgeChannelPredecessor?.patchId !== "device-edge-core-channel-upgrade-v4-20260812-023"
|
||||
|| descriptor.edgeChannelPredecessor?.artifactSha256 !== "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|
||||
|| descriptor.telemetryWorkspace !== "mission-core-compute-module-parity-v1"
|
||||
|| descriptor.telemetryNavigation !== "full-workspace-back-navigation-v1"
|
||||
|| descriptor.telemetryPollInterval !== "three-seconds"
|
||||
|| descriptor.telemetryFreshness !== "fifteen-seconds-missing-stale-not-unhealthy"
|
||||
|| descriptor.telemetryOntologyProjection !== "observation-observed-property-provenance-freshness-v1"
|
||||
|| descriptor.telemetryAgent !== "telegraf-host-observer-v1"
|
||||
|| descriptor.interactiveShell !== "disabled-pending-managed-session-boundary"
|
||||
|| descriptor.rollback !== "restore-preapply-snapshot-preserve-manager-data"
|
||||
|| descriptor.gelios !== "untouched-legacy-only"
|
||||
) throw new Error("device_manager_v10_host_telemetry_workspace_contract_mismatch");
|
||||
await validateFaviconBundle(payload, "device_manager_v10");
|
||||
} else if (descriptorPath.endsWith("release-v9.json")) {
|
||||
if (
|
||||
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v9"
|
||||
|| descriptor.predecessor?.kind !== "release"
|
||||
|
||||
@@ -1104,10 +1104,17 @@ def ensure_service_user(name=SERVICE_USER, home_dir="/var/lib/nodedc-b2-vps"):
|
||||
|
||||
def extract_vendor_binary(archive: Path, member_name: str, target: Path, mode=0o755):
|
||||
with tarfile.open(archive, "r:*") as package:
|
||||
try:
|
||||
member = package.getmember(member_name)
|
||||
except KeyError:
|
||||
accepted_names = {member_name, f"./{member_name}"}
|
||||
matches = [
|
||||
candidate
|
||||
for candidate in package.getmembers()
|
||||
if candidate.name in accepted_names
|
||||
]
|
||||
if not matches:
|
||||
die(f"vendor binary member missing: {member_name}")
|
||||
if len(matches) != 1:
|
||||
die(f"vendor binary member ambiguous: {member_name}")
|
||||
member = matches[0]
|
||||
if not member.isfile() or member.issym() or member.islnk():
|
||||
die(f"vendor binary member unsafe: {member_name}")
|
||||
source = package.extractfile(member)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import hashlib
|
||||
import io
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -479,6 +480,52 @@ class DeviceEdgeVpsArtifactTest(unittest.TestCase):
|
||||
"c7486ec879681ddd706f229b628c8556ca8c9ccc4f152a85debb409c302759ef",
|
||||
)
|
||||
|
||||
def test_vendor_binary_extractor_accepts_official_leading_dot_member(self):
|
||||
member_name = "telegraf-1.38.4/usr/bin/telegraf"
|
||||
binary = b"pinned-telegraf-binary"
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-vps-vendor-leading-dot-",
|
||||
) as directory:
|
||||
root = Path(directory)
|
||||
archive = root / "telegraf.tgz"
|
||||
target = root / "runtime/telegraf"
|
||||
with tarfile.open(archive, "w:gz") as package:
|
||||
member = tarfile.TarInfo(f"./{member_name}")
|
||||
member.size = len(binary)
|
||||
package.addfile(member, io.BytesIO(binary))
|
||||
with patch.object(RUNNER.os, "chown"):
|
||||
RUNNER.extract_vendor_binary(
|
||||
archive,
|
||||
member_name,
|
||||
target,
|
||||
)
|
||||
self.assertEqual(target.read_bytes(), binary)
|
||||
self.assertEqual(target.stat().st_mode & 0o777, 0o755)
|
||||
|
||||
def test_vendor_binary_extractor_rejects_ambiguous_spelling(self):
|
||||
member_name = "telegraf-1.38.4/usr/bin/telegraf"
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-vps-vendor-ambiguous-",
|
||||
) as directory:
|
||||
root = Path(directory)
|
||||
archive = root / "telegraf.tgz"
|
||||
target = root / "runtime/telegraf"
|
||||
with tarfile.open(archive, "w:gz") as package:
|
||||
for name in (member_name, f"./{member_name}"):
|
||||
member = tarfile.TarInfo(name)
|
||||
member.size = 1
|
||||
package.addfile(member, io.BytesIO(b"x"))
|
||||
with self.assertRaisesRegex(
|
||||
RUNNER.DeployError,
|
||||
"vendor binary member ambiguous",
|
||||
):
|
||||
RUNNER.extract_vendor_binary(
|
||||
archive,
|
||||
member_name,
|
||||
target,
|
||||
)
|
||||
self.assertFalse(target.exists())
|
||||
|
||||
def test_publish_payload_preserves_unselected_executable_modes(self):
|
||||
with tempfile.TemporaryDirectory(prefix="nodedc-vps-publish-scope-") as directory:
|
||||
root = Path(directory)
|
||||
|
||||
@@ -723,6 +723,259 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
|
||||
def test_device_manager_release_v10_pins_successful_telemetry_core(self):
|
||||
patch_id = "device-manager-release-v10-unit-001"
|
||||
manifest, entries, names, result = self.assert_deterministic_artifact(
|
||||
"build-device-manager-control-plane-artifact.mjs",
|
||||
patch_id,
|
||||
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V10_ENTRIES,
|
||||
)
|
||||
self.assertEqual(manifest["component"], "device-plane")
|
||||
self.assertEqual(result["services"], ["device-manager"])
|
||||
self.assertFalse(
|
||||
any(
|
||||
name.startswith("payload/services/device-control-core/")
|
||||
for name in names
|
||||
)
|
||||
)
|
||||
self.assertFalse(any(name.startswith("payload/packages/") for name in names))
|
||||
template = json.loads(
|
||||
(
|
||||
DEVICE_CORE_ROOT
|
||||
/ "deployment/device-manager-release-v10.json"
|
||||
).read_text(encoding="utf-8")
|
||||
)
|
||||
descriptor = {**template, "releaseId": patch_id}
|
||||
self.assertIs(
|
||||
RUNNER.validate_device_plane_manager_release_descriptor(
|
||||
descriptor,
|
||||
schema_version=(
|
||||
"nodedc.device-plane.device-manager-release.v10"
|
||||
),
|
||||
boundaries=(
|
||||
RUNNER.expected_device_plane_manager_release_v10_boundaries()
|
||||
),
|
||||
expected_release_id=patch_id,
|
||||
),
|
||||
descriptor,
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["predecessor"],
|
||||
{
|
||||
"kind": "release",
|
||||
"patchId": "device-manager-release-v8-20260822-039",
|
||||
"artifactSha256": (
|
||||
"30a83d4c6b5c029c96c4af19fa558e0ae75e4b60bc897881457304bd17e0e465"
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["controlCorePredecessor"],
|
||||
{
|
||||
"patchId": "device-control-core-release-v4-20260823-047",
|
||||
"artifactSha256": (
|
||||
"4aecceeb8d400fdd3dbec8fc86b151691f389d165e72677f396f8994823b6b5c"
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["telemetryWorkspace"],
|
||||
"mission-core-compute-module-parity-v1",
|
||||
)
|
||||
self.assertTrue(
|
||||
RUNNER.is_device_plane_manager_release_v10_slice(
|
||||
"device-plane",
|
||||
entries,
|
||||
)
|
||||
)
|
||||
|
||||
def test_device_manager_release_v11_pins_v10_and_visual_parity(self):
|
||||
patch_id = "device-manager-release-v11-unit-001"
|
||||
manifest, entries, names, result = self.assert_deterministic_artifact(
|
||||
"build-device-manager-control-plane-artifact.mjs",
|
||||
patch_id,
|
||||
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V11_ENTRIES,
|
||||
)
|
||||
self.assertEqual(manifest["component"], "device-plane")
|
||||
self.assertEqual(result["services"], ["device-manager"])
|
||||
self.assertFalse(
|
||||
any(
|
||||
name.startswith("payload/services/device-control-core/")
|
||||
for name in names
|
||||
)
|
||||
)
|
||||
self.assertFalse(any(name.startswith("payload/packages/") for name in names))
|
||||
template = json.loads(
|
||||
(
|
||||
DEVICE_CORE_ROOT
|
||||
/ "deployment/device-manager-release-v11.json"
|
||||
).read_text(encoding="utf-8")
|
||||
)
|
||||
descriptor = {**template, "releaseId": patch_id}
|
||||
self.assertIs(
|
||||
RUNNER.validate_device_plane_manager_release_descriptor(
|
||||
descriptor,
|
||||
schema_version=(
|
||||
"nodedc.device-plane.device-manager-release.v11"
|
||||
),
|
||||
boundaries=(
|
||||
RUNNER.expected_device_plane_manager_release_v11_boundaries()
|
||||
),
|
||||
expected_release_id=patch_id,
|
||||
),
|
||||
descriptor,
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["predecessor"],
|
||||
{
|
||||
"kind": "release",
|
||||
"patchId": "device-manager-release-v10-20260823-048",
|
||||
"artifactSha256": (
|
||||
"e6b983a314db4f8c27d89062dfedf5ed0523cc30421170799d181a19e2d85d4c"
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["telemetryWorkspace"],
|
||||
"mission-core-compute-module-visual-parity-v2",
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["telemetrySurface"],
|
||||
"borderless-soft-surface-v1",
|
||||
)
|
||||
self.assertTrue(
|
||||
RUNNER.is_device_plane_manager_release_v11_slice(
|
||||
"device-plane",
|
||||
entries,
|
||||
)
|
||||
)
|
||||
|
||||
def test_device_manager_release_v12_pins_v11_and_adaptive_graphs(self):
|
||||
patch_id = "device-manager-release-v12-unit-001"
|
||||
manifest, entries, names, result = self.assert_deterministic_artifact(
|
||||
"build-device-manager-control-plane-artifact.mjs",
|
||||
patch_id,
|
||||
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V12_ENTRIES,
|
||||
)
|
||||
self.assertEqual(manifest["component"], "device-plane")
|
||||
self.assertEqual(result["services"], ["device-manager"])
|
||||
self.assertFalse(
|
||||
any(
|
||||
name.startswith("payload/services/device-control-core/")
|
||||
for name in names
|
||||
)
|
||||
)
|
||||
self.assertFalse(any(name.startswith("payload/packages/") for name in names))
|
||||
template = json.loads(
|
||||
(
|
||||
DEVICE_CORE_ROOT
|
||||
/ "deployment/device-manager-release-v12.json"
|
||||
).read_text(encoding="utf-8")
|
||||
)
|
||||
descriptor = {**template, "releaseId": patch_id}
|
||||
self.assertIs(
|
||||
RUNNER.validate_device_plane_manager_release_descriptor(
|
||||
descriptor,
|
||||
schema_version=(
|
||||
"nodedc.device-plane.device-manager-release.v12"
|
||||
),
|
||||
boundaries=(
|
||||
RUNNER.expected_device_plane_manager_release_v12_boundaries()
|
||||
),
|
||||
expected_release_id=patch_id,
|
||||
),
|
||||
descriptor,
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["predecessor"],
|
||||
{
|
||||
"kind": "release",
|
||||
"patchId": "device-manager-release-v11-20260823-049",
|
||||
"artifactSha256": (
|
||||
"c1e2056b50bfbb0d03d077461d0c27cc56cc52967c3f5620be14871c8a6d5cf0"
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["telemetryGraphScale"],
|
||||
"adaptive-observed-window-explicit-domain-v1",
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["telemetryNetworkMissingSemantics"],
|
||||
"missing-counters-never-zero-v1",
|
||||
)
|
||||
self.assertTrue(
|
||||
RUNNER.is_device_plane_manager_release_v12_slice(
|
||||
"device-plane",
|
||||
entries,
|
||||
)
|
||||
)
|
||||
|
||||
def test_device_manager_release_v13_pins_v12_and_host_accordion(self):
|
||||
patch_id = "device-manager-release-v13-unit-001"
|
||||
manifest, entries, names, result = self.assert_deterministic_artifact(
|
||||
"build-device-manager-control-plane-artifact.mjs",
|
||||
patch_id,
|
||||
RUNNER.DEVICE_PLANE_MANAGER_RELEASE_V13_ENTRIES,
|
||||
)
|
||||
self.assertEqual(manifest["component"], "device-plane")
|
||||
self.assertEqual(result["services"], ["device-manager"])
|
||||
self.assertFalse(
|
||||
any(
|
||||
name.startswith("payload/services/device-control-core/")
|
||||
for name in names
|
||||
)
|
||||
)
|
||||
self.assertFalse(any(name.startswith("payload/packages/") for name in names))
|
||||
template = json.loads(
|
||||
(
|
||||
DEVICE_CORE_ROOT
|
||||
/ "deployment/device-manager-release-v13.json"
|
||||
).read_text(encoding="utf-8")
|
||||
)
|
||||
descriptor = {**template, "releaseId": patch_id}
|
||||
self.assertIs(
|
||||
RUNNER.validate_device_plane_manager_release_descriptor(
|
||||
descriptor,
|
||||
schema_version=(
|
||||
"nodedc.device-plane.device-manager-release.v13"
|
||||
),
|
||||
boundaries=(
|
||||
RUNNER.expected_device_plane_manager_release_v13_boundaries()
|
||||
),
|
||||
expected_release_id=patch_id,
|
||||
),
|
||||
descriptor,
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["predecessor"],
|
||||
{
|
||||
"kind": "release",
|
||||
"patchId": "device-manager-release-v12-20260823-050",
|
||||
"artifactSha256": (
|
||||
"1a49839140e5f2e49763d78f24ee47d946e244bcfde15a9c38266e8bd14c0d49"
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["hostInventoryRow"],
|
||||
"compact-centered-accordion-v1",
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["hostInventoryRelations"],
|
||||
"host-scoped-endpoint-deployment-service-v1",
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["telemetryWorkspace"],
|
||||
"mission-core-compute-module-adaptive-window-v3",
|
||||
)
|
||||
self.assertTrue(
|
||||
RUNNER.is_device_plane_manager_release_v13_slice(
|
||||
"device-plane",
|
||||
entries,
|
||||
)
|
||||
)
|
||||
|
||||
def test_historical_manager_builder_fails_closed_after_v4_compose(self):
|
||||
if self.historical_manager_compose_is_current():
|
||||
self.skipTest("historical Manager Compose is still current")
|
||||
@@ -1107,6 +1360,93 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(descriptor["gelios"], "untouched-legacy-only")
|
||||
|
||||
def test_control_core_release_v4_is_recovery_pinned_telemetry_core_only(self):
|
||||
patch_id = "device-control-core-release-v4-20260823-047"
|
||||
manifest, entries, names, result = self.assert_deterministic_artifact(
|
||||
"build-device-control-core-release-artifact.mjs",
|
||||
patch_id,
|
||||
RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_ENTRIES,
|
||||
)
|
||||
self.assertEqual(manifest["component"], "device-plane")
|
||||
self.assertEqual(result["services"], ["device-control-core"])
|
||||
self.assertEqual(
|
||||
RUNNER.component_services("device-plane", entries),
|
||||
("device-control-core",),
|
||||
)
|
||||
self.assertIn(
|
||||
"payload/deployment/device-control-core-release-v4.json",
|
||||
names,
|
||||
)
|
||||
self.assertIn(
|
||||
"payload/packages/infrastructure-telemetry-contract/src/index.mjs",
|
||||
names,
|
||||
)
|
||||
self.assertIn(
|
||||
"payload/services/device-control-core/migrations/"
|
||||
"017_infrastructure_host_telemetry.sql",
|
||||
names,
|
||||
)
|
||||
self.assertFalse(any("docker-compose" in name for name in names))
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-control-core-v4-read-",
|
||||
) as directory:
|
||||
result = self.build(
|
||||
"build-device-control-core-release-artifact.mjs",
|
||||
patch_id,
|
||||
Path(directory),
|
||||
)
|
||||
extracted = Path(directory) / "extracted"
|
||||
extracted.mkdir()
|
||||
_manifest, _entries, payload = RUNNER.load_artifact(
|
||||
Path(result["artifact"]),
|
||||
extracted,
|
||||
)
|
||||
descriptor = json.loads(
|
||||
(
|
||||
payload / RUNNER.DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_REL
|
||||
).read_text(encoding="utf-8")
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["predecessor"],
|
||||
{
|
||||
"kind": "migration-replay-checkpoint-recovery",
|
||||
"patchId": (
|
||||
"device-control-core-migration-replay-checkpoint-"
|
||||
"recovery-20260822-046"
|
||||
),
|
||||
"artifactSha256": (
|
||||
"46000c76977fb583fc7c9cf74ecf624efd8b404f7b8d0322e0270e7b8ac6e450"
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
descriptor["databaseSchemaOutcome"],
|
||||
"migration-017-host-telemetry-table-present",
|
||||
)
|
||||
|
||||
def test_control_core_release_v4_rejects_any_other_identity(self):
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-control-core-v4-wrong-id-",
|
||||
) as directory:
|
||||
environment = os.environ.copy()
|
||||
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = directory
|
||||
completed = subprocess.run(
|
||||
[
|
||||
"node",
|
||||
str(
|
||||
SCRIPT_DIR
|
||||
/ "build-device-control-core-release-artifact.mjs"
|
||||
),
|
||||
"device-control-core-release-v4-20260823-999",
|
||||
],
|
||||
cwd=DEVICE_CORE_ROOT,
|
||||
env=environment,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertNotEqual(completed.returncode, 0)
|
||||
self.assertIn("usage:", completed.stderr)
|
||||
|
||||
def test_control_core_release_builder_supports_release_predecessor(self):
|
||||
if not self.historical_control_core_builders_are_current():
|
||||
self.skipTest("historical Core release v1 builder generation is frozen")
|
||||
|
||||
Reference in New Issue
Block a user