feat(worker): install combined observatory profiles
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""Fixed POSIX service composition for the sealed portable M4.9 executor.
|
||||
"""Fixed POSIX service composition for installed M4.9 and LAB V1 executors.
|
||||
|
||||
The entrypoint reads only immutable registries, one installation receipt and
|
||||
path-only Worker service settings. Jobs cannot select commands, providers,
|
||||
The entrypoint reads only immutable registries, exact external release files
|
||||
and path-only Worker service settings. Jobs cannot select commands, providers,
|
||||
modules, images or filesystem locations. Every runtime asset is resolved from
|
||||
the fixed release layout and re-verified against the ready runtime candidate
|
||||
before the first queue claim.
|
||||
@@ -15,6 +15,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import socket
|
||||
import stat
|
||||
from collections.abc import Iterator, Mapping, Sequence
|
||||
from contextlib import contextmanager
|
||||
@@ -35,6 +36,22 @@ from k1link.observatory.m49_portable_executor import (
|
||||
M49PortableRunnerInstallation,
|
||||
compose_m49_portable_executor_adapter,
|
||||
)
|
||||
from k1link.observatory.portable_lab_v1_executor import (
|
||||
PortableLabV1ReleaseAsset,
|
||||
PortableLabV1ReleaseCandidate,
|
||||
PortableLabV1ReleaseInspection,
|
||||
)
|
||||
from k1link.observatory.portable_lab_v1_worker import (
|
||||
PortableLabV1RunnerInstallation as PortableLabV1ReleaseInstallation,
|
||||
)
|
||||
from k1link.observatory.portable_lab_v1_worker_service import (
|
||||
PORTABLE_LAB_V1_ADAPTER_ID,
|
||||
PORTABLE_LAB_V1_SETUP_ID,
|
||||
PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID,
|
||||
PortableLabV1WorkerInstallationReceipt,
|
||||
compose_installed_lab_v1_executor_builder,
|
||||
load_portable_lab_v1_worker_installation_receipt,
|
||||
)
|
||||
from k1link.observatory.portable_result_contract import OBSERVATION_ONLY_AUTHORITY
|
||||
from k1link.observatory.portable_run_definitions import (
|
||||
PortableRunDefinition,
|
||||
@@ -64,6 +81,12 @@ M49_WORKER_RUNTIME_REGISTRY_FILE_ENV: Final = "MISSIONCORE_OBSERVATORY_WORKER_RU
|
||||
M49_WORKER_INSTALLATION_RECEIPT_FILE_ENV: Final = (
|
||||
"MISSIONCORE_OBSERVATORY_M49_INSTALLATION_RECEIPT_FILE"
|
||||
)
|
||||
LAB_V1_WORKER_INSTALLATION_RECEIPT_FILE_ENV: Final = (
|
||||
"MISSIONCORE_OBSERVATORY_LAB_V1_INSTALLATION_RECEIPT_FILE"
|
||||
)
|
||||
LAB_V1_WORKER_RELEASE_CANDIDATE_FILE_ENV: Final = (
|
||||
"MISSIONCORE_OBSERVATORY_LAB_V1_RELEASE_CANDIDATE_FILE"
|
||||
)
|
||||
|
||||
M49_WORKER_INSTALLATION_RECEIPT_SCHEMA: Final = (
|
||||
"missioncore.m49-tgs-portable-worker-installation-ready-receipt/v1"
|
||||
@@ -74,12 +97,24 @@ M49_WORKER_RELEASE_ID: Final = "m49-tgs-portable-executor-v1"
|
||||
|
||||
M49_EXECUTOR_RELEASE_ASSET_ID: Final = "m49-portable-executor-release"
|
||||
M49_WORKER_INSTALLATION_RECEIPT_ASSET_ID: Final = "m49-portable-worker-installation-receipt"
|
||||
LAB_V1_PORTABLE_CONFIG_ASSET_ID: Final = "ddrnet-portable-config"
|
||||
LAB_V1_WORKER_AGENT_IMAGE_ASSET_ID: Final = "worker-006-agent-image"
|
||||
|
||||
_MAX_RECEIPT_BYTES: Final = 256 * 1024
|
||||
_MAX_DOCKER_INSPECTION_BYTES: Final = 1024 * 1024
|
||||
_SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
|
||||
_CONTAINER_HOSTNAME: Final = re.compile(r"^[a-f0-9]{12,64}$")
|
||||
_SOURCE_REVISION: Final = re.compile(r"^[a-f0-9]{40}$")
|
||||
_WORKER_COMPUTER_NAME: Final = "DESKTOP-OPJ8J04"
|
||||
_DOCKER_SOCKET: Final = Path("/var/run/docker.sock")
|
||||
_DOCKER_API_VERSION: Final = "v1.47"
|
||||
_FIXED_RUNTIME_RELEASE_ROOT: Final = Path("/release")
|
||||
_FIXED_LAB_V1_INSTALLATION_RECEIPT_FILE: Final = (
|
||||
_FIXED_RUNTIME_RELEASE_ROOT / "lab-v1-worker-installation-receipt.json"
|
||||
)
|
||||
_FIXED_LAB_V1_RELEASE_CANDIDATE_FILE: Final = (
|
||||
_FIXED_RUNTIME_RELEASE_ROOT / "lab-v1-executor-release.json"
|
||||
)
|
||||
_PROTECTED_RUNTIME_NAMES: Final = (
|
||||
"ndc-mission-core-triton",
|
||||
"ndc-mission-core-perception-worker",
|
||||
@@ -119,6 +154,8 @@ class M49WorkerEntrypointConfiguration:
|
||||
definitions_file: Path
|
||||
runtime_registry_file: Path
|
||||
installation_receipt_file: Path
|
||||
lab_v1_installation_receipt_file: Path | None = None
|
||||
lab_v1_release_candidate_file: Path | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
for path, label in (
|
||||
@@ -127,6 +164,33 @@ class M49WorkerEntrypointConfiguration:
|
||||
(self.installation_receipt_file, "M4.9 installation receipt"),
|
||||
):
|
||||
_absolute_path(path, label)
|
||||
if (self.lab_v1_installation_receipt_file is None) != (
|
||||
self.lab_v1_release_candidate_file is None
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 installation receipt and release candidate must be configured together"
|
||||
)
|
||||
for optional_path, label in (
|
||||
(
|
||||
self.lab_v1_installation_receipt_file,
|
||||
"LAB V1 installation receipt",
|
||||
),
|
||||
(self.lab_v1_release_candidate_file, "LAB V1 release candidate"),
|
||||
):
|
||||
if optional_path is not None:
|
||||
_absolute_path(optional_path, label)
|
||||
if (
|
||||
self.lab_v1_installation_receipt_file is not None
|
||||
and (
|
||||
self.lab_v1_installation_receipt_file
|
||||
!= _FIXED_LAB_V1_INSTALLATION_RECEIPT_FILE
|
||||
or self.lab_v1_release_candidate_file
|
||||
!= _FIXED_LAB_V1_RELEASE_CANDIDATE_FILE
|
||||
)
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 production inputs must use the fixed /release files"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_environment(
|
||||
@@ -148,6 +212,14 @@ class M49WorkerEntrypointConfiguration:
|
||||
values,
|
||||
M49_WORKER_INSTALLATION_RECEIPT_FILE_ENV,
|
||||
),
|
||||
lab_v1_installation_receipt_file=_required_environment_path(
|
||||
values,
|
||||
LAB_V1_WORKER_INSTALLATION_RECEIPT_FILE_ENV,
|
||||
),
|
||||
lab_v1_release_candidate_file=_required_environment_path(
|
||||
values,
|
||||
LAB_V1_WORKER_RELEASE_CANDIDATE_FILE_ENV,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -483,15 +555,374 @@ def compose_installed_m49_worker_service(
|
||||
installation=installation,
|
||||
),
|
||||
)
|
||||
installed_builders = list(executor_builders)
|
||||
if configuration.lab_v1_installation_receipt_file is not None:
|
||||
installed_builders.insert(
|
||||
0,
|
||||
_compose_installed_lab_v1_builder(
|
||||
configuration=configuration,
|
||||
definitions=definitions,
|
||||
runtime_registry=runtime_registry,
|
||||
),
|
||||
)
|
||||
return compose_installed_observatory_worker_service_from_builders(
|
||||
configuration=configuration.worker,
|
||||
definitions=definitions,
|
||||
runtime_registry=runtime_registry,
|
||||
builders=(m49_builder, *executor_builders),
|
||||
builders=(m49_builder, *installed_builders),
|
||||
http_transport=http_transport,
|
||||
)
|
||||
|
||||
|
||||
def _compose_installed_lab_v1_builder(
|
||||
*,
|
||||
configuration: M49WorkerEntrypointConfiguration,
|
||||
definitions: PortableRunDefinitionRegistry,
|
||||
runtime_registry: PortableWorkerRuntimeRegistry,
|
||||
) -> ObservatoryWorkerExecutorBuilderRegistration:
|
||||
receipt_path = configuration.lab_v1_installation_receipt_file
|
||||
release_path = configuration.lab_v1_release_candidate_file
|
||||
if receipt_path is None or release_path is None:
|
||||
raise M49WorkerCompositionError("LAB V1 production inputs are incomplete")
|
||||
definition = definitions.resolve_setup(PORTABLE_LAB_V1_SETUP_ID)
|
||||
candidate = runtime_registry.resolve(
|
||||
definition.setup_id,
|
||||
definition.definition_sha256,
|
||||
)
|
||||
_verify_ready_lab_v1_identity(definitions, definition, candidate)
|
||||
receipt = load_portable_lab_v1_worker_installation_receipt(receipt_path)
|
||||
release_file = _regular_file(release_path, "LAB V1 release candidate")
|
||||
release = PortableLabV1ReleaseCandidate.from_file(
|
||||
release_file,
|
||||
repository_root=_real_directory(
|
||||
release_file.parent,
|
||||
"LAB V1 release repository root",
|
||||
),
|
||||
)
|
||||
if receipt.release_candidate_sha256 != release.candidate_sha256:
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 installation receipt belongs to another release candidate"
|
||||
)
|
||||
release.bind_definition(definition)
|
||||
inspection = _inspect_installed_lab_v1_release(
|
||||
release=release,
|
||||
receipt=receipt,
|
||||
definition=definition,
|
||||
running_worker_image_sha256=(
|
||||
_inspect_running_worker_container_image_sha256()
|
||||
),
|
||||
)
|
||||
bindings = _lab_v1_runtime_asset_bindings(
|
||||
candidate=candidate,
|
||||
release=release,
|
||||
receipt=receipt,
|
||||
)
|
||||
admission = inspect_runtime_candidate(candidate, bindings)
|
||||
if not admission.ready:
|
||||
raise M49WorkerCompositionError(
|
||||
"portable LAB V1 local asset admission is not ready"
|
||||
)
|
||||
portable_config_path = _lab_v1_release_repository_file(
|
||||
release,
|
||||
LAB_V1_PORTABLE_CONFIG_ASSET_ID,
|
||||
)
|
||||
release_installation = PortableLabV1ReleaseInstallation(
|
||||
release=release,
|
||||
inspection=inspection,
|
||||
portable_ddrnet_config_path=portable_config_path,
|
||||
output_parent=(
|
||||
configuration.worker.work_root / "lab-v1-portable" / "runner-output"
|
||||
),
|
||||
)
|
||||
return compose_installed_lab_v1_executor_builder(
|
||||
receipt=receipt,
|
||||
definition=definition,
|
||||
candidate=candidate,
|
||||
admission=admission,
|
||||
release_installation=release_installation,
|
||||
)
|
||||
|
||||
|
||||
def _verify_ready_lab_v1_identity(
|
||||
definitions: PortableRunDefinitionRegistry,
|
||||
definition: PortableRunDefinition,
|
||||
candidate: PortableWorkerRuntimeCandidate,
|
||||
) -> None:
|
||||
ready_identities = {
|
||||
(item.setup_id, item.definition_sha256)
|
||||
for item in definitions.ready_recorded_definitions()
|
||||
}
|
||||
if (
|
||||
(definition.setup_id, definition.definition_sha256) not in ready_identities
|
||||
or definition.setup_id != PORTABLE_LAB_V1_SETUP_ID
|
||||
or definition.executor.contour_id != WORKER_006_CONTOUR_ID
|
||||
or candidate.adapter_id != PORTABLE_LAB_V1_ADAPTER_ID
|
||||
or not definition.executor.ready
|
||||
or not candidate.ready
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"fixed Worker requires the exact ready LAB V1 definition"
|
||||
)
|
||||
|
||||
|
||||
def _lab_v1_runtime_asset_bindings(
|
||||
*,
|
||||
candidate: PortableWorkerRuntimeCandidate,
|
||||
release: PortableLabV1ReleaseCandidate,
|
||||
receipt: PortableLabV1WorkerInstallationReceipt,
|
||||
) -> dict[str, PortableWorkerLocalAssetBinding]:
|
||||
release_assets = {asset.asset_id: asset for asset in release.assets}
|
||||
bindings: dict[str, PortableWorkerLocalAssetBinding] = {}
|
||||
for requirement in candidate.reusable_assets:
|
||||
if (
|
||||
requirement.asset_id
|
||||
== PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID
|
||||
):
|
||||
if (
|
||||
requirement.kind != "local-file"
|
||||
or requirement.sha256 != receipt.file_sha256
|
||||
or requirement.byte_length != receipt.file_byte_length
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 runtime installation receipt anchor changed"
|
||||
)
|
||||
bindings[requirement.asset_id] = PortableWorkerLocalAssetBinding(
|
||||
asset_id=requirement.asset_id,
|
||||
file_path=receipt.path,
|
||||
)
|
||||
continue
|
||||
asset = release_assets.get(requirement.asset_id)
|
||||
if (
|
||||
asset is None
|
||||
or asset.sha256 != requirement.sha256
|
||||
or asset.byte_length != requirement.byte_length
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 runtime asset differs from its exact release candidate"
|
||||
)
|
||||
if requirement.kind == "container-image" and asset.kind == "container-image":
|
||||
bindings[requirement.asset_id] = PortableWorkerLocalAssetBinding(
|
||||
asset_id=requirement.asset_id,
|
||||
image_sha256=asset.sha256,
|
||||
)
|
||||
continue
|
||||
if asset.repository_path is None:
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 non-image runtime asset has no exact release file"
|
||||
)
|
||||
bindings[requirement.asset_id] = PortableWorkerLocalAssetBinding(
|
||||
asset_id=requirement.asset_id,
|
||||
file_path=_lab_v1_release_repository_file(
|
||||
release,
|
||||
requirement.asset_id,
|
||||
),
|
||||
)
|
||||
return bindings
|
||||
|
||||
|
||||
def _inspect_installed_lab_v1_release(
|
||||
*,
|
||||
release: PortableLabV1ReleaseCandidate,
|
||||
receipt: PortableLabV1WorkerInstallationReceipt,
|
||||
definition: PortableRunDefinition,
|
||||
running_worker_image_sha256: str,
|
||||
) -> PortableLabV1ReleaseInspection:
|
||||
if release.declared_blockers or release.executor_image_sha256 is None:
|
||||
raise M49WorkerCompositionError("LAB V1 release candidate is not ready")
|
||||
_digest(
|
||||
running_worker_image_sha256,
|
||||
"running Worker container image SHA-256",
|
||||
)
|
||||
worker_image_assets = tuple(
|
||||
asset
|
||||
for asset in release.assets
|
||||
if asset.asset_id == LAB_V1_WORKER_AGENT_IMAGE_ASSET_ID
|
||||
)
|
||||
if (
|
||||
len(worker_image_assets) != 1
|
||||
or worker_image_assets[0].kind != "container-image"
|
||||
or worker_image_assets[0].sha256 != running_worker_image_sha256
|
||||
or worker_image_assets[0].byte_length is not None
|
||||
or worker_image_assets[0].repository_path is not None
|
||||
or release.executor_image_sha256 != running_worker_image_sha256
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 executor image differs from the running Worker container"
|
||||
)
|
||||
installed_evidence = _lab_v1_installed_release_evidence(receipt)
|
||||
installed_images = {
|
||||
receipt.eomt_image_build.base_image_sha256,
|
||||
receipt.eomt_image_build.derived_image_sha256,
|
||||
receipt.ddrnet_image_build.base_image_sha256,
|
||||
receipt.ddrnet_image_build.derived_image_sha256,
|
||||
running_worker_image_sha256,
|
||||
}
|
||||
definition_components = {component.sha256 for component in definition.components}
|
||||
matched: list[str] = []
|
||||
for asset in release.assets:
|
||||
if (
|
||||
asset.asset_id
|
||||
== PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 installation receipt must remain a runtime-only anchor"
|
||||
)
|
||||
if asset.kind == "repository-file":
|
||||
_lab_v1_release_repository_file(release, asset.asset_id)
|
||||
elif asset.kind == "container-image":
|
||||
if asset.byte_length is not None or asset.sha256 not in installed_images:
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 release contains an uninstalled container image"
|
||||
)
|
||||
elif asset.kind == "definition-component":
|
||||
if asset.byte_length is not None or asset.sha256 not in definition_components:
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 release contains an unknown definition component"
|
||||
)
|
||||
elif not _lab_v1_release_evidence_matches(asset, installed_evidence):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 release contains an asset absent from its installation receipt"
|
||||
)
|
||||
matched.append(asset.asset_id)
|
||||
return PortableLabV1ReleaseInspection(
|
||||
candidate_sha256=release.candidate_sha256,
|
||||
matched_assets=tuple(matched),
|
||||
blockers=(),
|
||||
ready=True,
|
||||
)
|
||||
|
||||
|
||||
def _inspect_running_worker_container_image_sha256(
|
||||
*,
|
||||
container_hostname: str | None = None,
|
||||
transport: httpx.BaseTransport | None = None,
|
||||
docker_socket: Path = _DOCKER_SOCKET,
|
||||
) -> str:
|
||||
"""Return the exact image ID of this running Worker container."""
|
||||
|
||||
hostname = socket.gethostname() if container_hostname is None else container_hostname
|
||||
if _CONTAINER_HOSTNAME.fullmatch(hostname) is None:
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker container hostname is not an exact container ID"
|
||||
)
|
||||
selected_transport = transport
|
||||
if selected_transport is None:
|
||||
candidate = docker_socket.expanduser().absolute()
|
||||
try:
|
||||
metadata = candidate.lstat()
|
||||
except OSError as exc:
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker Docker Engine socket is unavailable"
|
||||
) from exc
|
||||
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISSOCK(metadata.st_mode):
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker Docker Engine socket is unsafe"
|
||||
)
|
||||
selected_transport = httpx.HTTPTransport(uds=str(candidate))
|
||||
try:
|
||||
with httpx.Client(
|
||||
base_url="http://docker",
|
||||
transport=selected_transport,
|
||||
timeout=5.0,
|
||||
) as client:
|
||||
response = client.get(
|
||||
f"/{_DOCKER_API_VERSION}/containers/{hostname}/json"
|
||||
)
|
||||
payload = response.content
|
||||
except (httpx.HTTPError, OSError) as exc:
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker container inspection failed"
|
||||
) from exc
|
||||
if response.status_code != 200 or not 0 < len(payload) <= _MAX_DOCKER_INSPECTION_BYTES:
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker container inspection is unavailable"
|
||||
)
|
||||
try:
|
||||
document = json.loads(payload.decode("utf-8"))
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker container inspection is invalid"
|
||||
) from exc
|
||||
row = _object(document, "running Worker container inspection")
|
||||
container_id = _string(row.get("Id"), "running Worker container ID")
|
||||
image_reference = _string(
|
||||
row.get("Image"),
|
||||
"running Worker container image ID",
|
||||
)
|
||||
state = _object(row.get("State"), "running Worker container state")
|
||||
image_sha256 = image_reference.removeprefix("sha256:")
|
||||
if (
|
||||
_SHA256.fullmatch(container_id) is None
|
||||
or not container_id.startswith(hostname)
|
||||
or image_reference != f"sha256:{image_sha256}"
|
||||
or _SHA256.fullmatch(image_sha256) is None
|
||||
or state.get("Running") is not True
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"running Worker container identity changed"
|
||||
)
|
||||
return image_sha256
|
||||
|
||||
|
||||
def _lab_v1_installed_release_evidence(
|
||||
receipt: PortableLabV1WorkerInstallationReceipt,
|
||||
) -> dict[str, set[int | None]]:
|
||||
evidence: dict[str, set[int | None]] = {}
|
||||
|
||||
def add(digest: str, byte_length: int | None) -> None:
|
||||
evidence.setdefault(digest, set()).add(byte_length)
|
||||
|
||||
for component in (
|
||||
receipt.runner_installation.eomt,
|
||||
receipt.runner_installation.ddrnet,
|
||||
):
|
||||
add(component.image_sha256, None)
|
||||
add(component.installation_sha256, None)
|
||||
for asset in component.assets:
|
||||
add(asset.identity_sha256, asset.byte_length)
|
||||
add(receipt.runner_installation.receipt_sha256, None)
|
||||
for build in (receipt.eomt_image_build, receipt.ddrnet_image_build):
|
||||
for key, value in build.identity_document().items():
|
||||
if key.endswith("_sha256") and isinstance(value, str):
|
||||
add(value, None)
|
||||
add(build.seal_sha256, None)
|
||||
return evidence
|
||||
|
||||
|
||||
def _lab_v1_release_evidence_matches(
|
||||
asset: PortableLabV1ReleaseAsset,
|
||||
evidence: Mapping[str, set[int | None]],
|
||||
) -> bool:
|
||||
lengths = evidence.get(asset.sha256)
|
||||
if lengths is None:
|
||||
return False
|
||||
return asset.byte_length is None or asset.byte_length in lengths
|
||||
|
||||
|
||||
def _lab_v1_release_repository_file(
|
||||
release: PortableLabV1ReleaseCandidate,
|
||||
asset_id: str,
|
||||
) -> Path:
|
||||
asset = next((row for row in release.assets if row.asset_id == asset_id), None)
|
||||
if asset is None or asset.kind != "repository-file" or asset.repository_path is None:
|
||||
raise M49WorkerCompositionError("LAB V1 exact release file is unavailable")
|
||||
relative = _safe_relative_path(asset.repository_path)
|
||||
path = _regular_file(
|
||||
release.repository_root.joinpath(*relative.parts),
|
||||
"LAB V1 release file",
|
||||
)
|
||||
if not path.is_relative_to(release.repository_root):
|
||||
raise M49WorkerCompositionError("LAB V1 release file escapes its release root")
|
||||
if (
|
||||
(asset.byte_length is not None and path.stat().st_size != asset.byte_length)
|
||||
or _sha256_file(path) != asset.sha256
|
||||
):
|
||||
raise M49WorkerCompositionError(
|
||||
"LAB V1 release file differs from its release candidate"
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def run_installed_m49_worker(
|
||||
service: InstalledObservatoryWorkerService,
|
||||
*,
|
||||
|
||||
@@ -94,6 +94,9 @@ _EXPECTED_RESULT_CONTRACT_SHA256: Final = (
|
||||
"b3dfaa8e20a0f22fc510d062ac469f010a3281c650059d9ea134f0b3ccb38d9a"
|
||||
)
|
||||
_PORTABLE_CONFIG_ASSET_ID: Final = "ddrnet-portable-config"
|
||||
_WORKER_INSTALLATION_RECEIPT_ASSET_ID: Final = (
|
||||
"lab-v1-worker-installation-receipt"
|
||||
)
|
||||
_MAX_SOURCE_DOCUMENT_BYTES: Final = 8 * 1024 * 1024
|
||||
_MAX_MATERIALIZATION_MANIFEST_BYTES: Final = 64 * 1024 * 1024
|
||||
_MAX_SOURCE_MEMBERS: Final = 100_000
|
||||
@@ -582,6 +585,12 @@ def _verify_candidate_release(
|
||||
):
|
||||
raise PortableLabV1WorkerError("portable LAB V1 runtime and release candidates disagree")
|
||||
for requirement in candidate.reusable_assets:
|
||||
if requirement.asset_id == _WORKER_INSTALLATION_RECEIPT_ASSET_ID:
|
||||
if requirement.kind != "local-file":
|
||||
raise PortableLabV1WorkerError(
|
||||
"portable LAB V1 installation receipt runtime anchor changed"
|
||||
)
|
||||
continue
|
||||
asset = release_assets.get(requirement.asset_id)
|
||||
if (
|
||||
asset is None
|
||||
|
||||
@@ -63,10 +63,13 @@ from k1link.observatory.worker_service import (
|
||||
)
|
||||
|
||||
PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_SCHEMA: Final = (
|
||||
"missioncore.observatory-portable-lab-v1-worker-installation-ready-receipt/v1"
|
||||
"missioncore.observatory-portable-lab-v1-worker-installation-ready-receipt/v2"
|
||||
)
|
||||
PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_SEAL_SCHEMA: Final = (
|
||||
"missioncore.observatory-portable-lab-v1-component-image-build-seal/v1"
|
||||
"missioncore.observatory-portable-lab-v1-component-image-build-seal/v2"
|
||||
)
|
||||
PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_METHOD: Final = (
|
||||
"docker-commit-exact-layer-v1"
|
||||
)
|
||||
PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_ASSET_ID: Final = (
|
||||
"lab-v1-worker-installation-receipt"
|
||||
@@ -98,6 +101,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
base_image_sha256: str
|
||||
derived_image_sha256: str
|
||||
dockerfile_sha256: str
|
||||
build_method: str
|
||||
installer_sha256: str
|
||||
shared_adapter_sha256: str
|
||||
component_adapter_sha256: str
|
||||
network: str
|
||||
@@ -112,6 +117,7 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
(self.base_image_sha256, "LAB V1 component base image SHA-256"),
|
||||
(self.derived_image_sha256, "LAB V1 derived component image SHA-256"),
|
||||
(self.dockerfile_sha256, "LAB V1 component Dockerfile SHA-256"),
|
||||
(self.installer_sha256, "LAB V1 component image installer SHA-256"),
|
||||
(self.shared_adapter_sha256, "LAB V1 shared adapter SHA-256"),
|
||||
(self.component_adapter_sha256, "LAB V1 component adapter SHA-256"),
|
||||
(self.seal_sha256, "LAB V1 component image build seal SHA-256"),
|
||||
@@ -120,6 +126,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
if (
|
||||
self.base_image_sha256
|
||||
!= _COMPONENT_BASE_IMAGE_SHA256S[self.component]
|
||||
or self.build_method
|
||||
!= PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_METHOD
|
||||
or self.network != "none"
|
||||
or self.seal_sha256
|
||||
!= hashlib.sha256(canonical_json(self.identity_document())).hexdigest()
|
||||
@@ -136,6 +144,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
base_image_sha256: str,
|
||||
derived_image_sha256: str,
|
||||
dockerfile_sha256: str,
|
||||
build_method: str,
|
||||
installer_sha256: str,
|
||||
shared_adapter_sha256: str,
|
||||
component_adapter_sha256: str,
|
||||
) -> PortableLabV1ComponentImageBuildSeal:
|
||||
@@ -144,6 +154,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
base_image_sha256=base_image_sha256,
|
||||
derived_image_sha256=derived_image_sha256,
|
||||
dockerfile_sha256=dockerfile_sha256,
|
||||
build_method=build_method,
|
||||
installer_sha256=installer_sha256,
|
||||
shared_adapter_sha256=shared_adapter_sha256,
|
||||
component_adapter_sha256=component_adapter_sha256,
|
||||
)
|
||||
@@ -152,6 +164,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
base_image_sha256=base_image_sha256,
|
||||
derived_image_sha256=derived_image_sha256,
|
||||
dockerfile_sha256=dockerfile_sha256,
|
||||
build_method=build_method,
|
||||
installer_sha256=installer_sha256,
|
||||
shared_adapter_sha256=shared_adapter_sha256,
|
||||
component_adapter_sha256=component_adapter_sha256,
|
||||
network="none",
|
||||
@@ -164,6 +178,8 @@ class PortableLabV1ComponentImageBuildSeal:
|
||||
base_image_sha256=self.base_image_sha256,
|
||||
derived_image_sha256=self.derived_image_sha256,
|
||||
dockerfile_sha256=self.dockerfile_sha256,
|
||||
build_method=self.build_method,
|
||||
installer_sha256=self.installer_sha256,
|
||||
shared_adapter_sha256=self.shared_adapter_sha256,
|
||||
component_adapter_sha256=self.component_adapter_sha256,
|
||||
)
|
||||
@@ -181,6 +197,7 @@ class PortableLabV1WorkerInstallationReceipt:
|
||||
runner_installation: PortableLabV1ComponentRunnerInstallation
|
||||
eomt_image_build: PortableLabV1ComponentImageBuildSeal
|
||||
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal
|
||||
installation_evidence_sha256: str
|
||||
receipt_sha256: str
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@@ -193,6 +210,10 @@ class PortableLabV1WorkerInstallationReceipt:
|
||||
"LAB V1 installation receipt size is invalid"
|
||||
)
|
||||
_digest(self.file_sha256, "LAB V1 installation receipt file SHA-256")
|
||||
_digest(
|
||||
self.installation_evidence_sha256,
|
||||
"LAB V1 external installation evidence SHA-256",
|
||||
)
|
||||
if _SOURCE_REVISION.fullmatch(self.source_revision) is None:
|
||||
raise PortableLabV1WorkerCompositionError(
|
||||
"LAB V1 installation receipt source revision is invalid"
|
||||
@@ -223,6 +244,7 @@ class PortableLabV1WorkerInstallationReceipt:
|
||||
runner_installation=self.runner_installation,
|
||||
eomt_image_build=self.eomt_image_build,
|
||||
ddrnet_image_build=self.ddrnet_image_build,
|
||||
installation_evidence_sha256=self.installation_evidence_sha256,
|
||||
):
|
||||
raise PortableLabV1WorkerCompositionError(
|
||||
"LAB V1 installation receipt identity changed"
|
||||
@@ -309,7 +331,8 @@ def load_portable_lab_v1_worker_installation_receipt(
|
||||
"runner_installation",
|
||||
"component_installations",
|
||||
"component_image_build_seals",
|
||||
"fixture_smoke",
|
||||
"installation_evidence_sha256",
|
||||
"installation_smoke",
|
||||
"blockers",
|
||||
"authority",
|
||||
"receipt_sha256",
|
||||
@@ -322,7 +345,7 @@ def load_portable_lab_v1_worker_installation_receipt(
|
||||
!= PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_SCHEMA
|
||||
or row["receipt_state"] != "installed-ready"
|
||||
or row["worker_id"] != _WORKER_ID
|
||||
or row["fixture_smoke"] != "passed"
|
||||
or row["installation_smoke"] != "offline-import-passed"
|
||||
or blockers
|
||||
or row["authority"] != OBSERVATION_ONLY_AUTHORITY
|
||||
):
|
||||
@@ -385,6 +408,10 @@ def load_portable_lab_v1_worker_installation_receipt(
|
||||
runner_installation=runner,
|
||||
eomt_image_build=eomt_image_build,
|
||||
ddrnet_image_build=ddrnet_image_build,
|
||||
installation_evidence_sha256=_string(
|
||||
row["installation_evidence_sha256"],
|
||||
"LAB V1 external installation evidence SHA-256",
|
||||
),
|
||||
receipt_sha256=_string(
|
||||
row["receipt_sha256"],
|
||||
"LAB V1 installation receipt SHA-256",
|
||||
@@ -501,6 +528,7 @@ def portable_lab_v1_worker_receipt_document(
|
||||
runner_installation: PortableLabV1ComponentRunnerInstallation,
|
||||
eomt_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
installation_evidence_sha256: str,
|
||||
) -> dict[str, object]:
|
||||
"""Return the canonical serializable receipt document for an installer."""
|
||||
|
||||
@@ -510,6 +538,7 @@ def portable_lab_v1_worker_receipt_document(
|
||||
runner_installation=runner_installation,
|
||||
eomt_image_build=eomt_image_build,
|
||||
ddrnet_image_build=ddrnet_image_build,
|
||||
installation_evidence_sha256=installation_evidence_sha256,
|
||||
)
|
||||
return {**identity, "receipt_sha256": hashlib.sha256(canonical_json(identity)).hexdigest()}
|
||||
|
||||
@@ -521,6 +550,7 @@ def _receipt_identity_sha256(
|
||||
runner_installation: PortableLabV1ComponentRunnerInstallation,
|
||||
eomt_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
installation_evidence_sha256: str,
|
||||
) -> str:
|
||||
return hashlib.sha256(
|
||||
canonical_json(
|
||||
@@ -530,6 +560,7 @@ def _receipt_identity_sha256(
|
||||
runner_installation=runner_installation,
|
||||
eomt_image_build=eomt_image_build,
|
||||
ddrnet_image_build=ddrnet_image_build,
|
||||
installation_evidence_sha256=installation_evidence_sha256,
|
||||
)
|
||||
)
|
||||
).hexdigest()
|
||||
@@ -542,7 +573,12 @@ def _receipt_identity_document(
|
||||
runner_installation: PortableLabV1ComponentRunnerInstallation,
|
||||
eomt_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
ddrnet_image_build: PortableLabV1ComponentImageBuildSeal,
|
||||
installation_evidence_sha256: str,
|
||||
) -> dict[str, object]:
|
||||
_digest(
|
||||
installation_evidence_sha256,
|
||||
"LAB V1 external installation evidence SHA-256",
|
||||
)
|
||||
return {
|
||||
"schema_version": PORTABLE_LAB_V1_WORKER_INSTALLATION_RECEIPT_SCHEMA,
|
||||
"receipt_state": "installed-ready",
|
||||
@@ -561,7 +597,8 @@ def _receipt_identity_document(
|
||||
"eomt": _component_image_build_document(eomt_image_build),
|
||||
"ddrnet": _component_image_build_document(ddrnet_image_build),
|
||||
},
|
||||
"fixture_smoke": "passed",
|
||||
"installation_evidence_sha256": installation_evidence_sha256,
|
||||
"installation_smoke": "offline-import-passed",
|
||||
"blockers": [],
|
||||
"authority": dict(OBSERVATION_ONLY_AUTHORITY),
|
||||
}
|
||||
@@ -582,6 +619,8 @@ def _component_image_build_identity(
|
||||
base_image_sha256: str,
|
||||
derived_image_sha256: str,
|
||||
dockerfile_sha256: str,
|
||||
build_method: str,
|
||||
installer_sha256: str,
|
||||
shared_adapter_sha256: str,
|
||||
component_adapter_sha256: str,
|
||||
) -> dict[str, object]:
|
||||
@@ -591,6 +630,8 @@ def _component_image_build_identity(
|
||||
"base_image_sha256": base_image_sha256,
|
||||
"derived_image_sha256": derived_image_sha256,
|
||||
"dockerfile_sha256": dockerfile_sha256,
|
||||
"build_method": build_method,
|
||||
"installer_sha256": installer_sha256,
|
||||
"shared_adapter_sha256": shared_adapter_sha256,
|
||||
"component_adapter_sha256": component_adapter_sha256,
|
||||
"network": "none",
|
||||
@@ -618,6 +659,8 @@ def _component_image_build_seal(
|
||||
"base_image_sha256",
|
||||
"derived_image_sha256",
|
||||
"dockerfile_sha256",
|
||||
"build_method",
|
||||
"installer_sha256",
|
||||
"shared_adapter_sha256",
|
||||
"component_adapter_sha256",
|
||||
"network",
|
||||
@@ -649,6 +692,14 @@ def _component_image_build_seal(
|
||||
row["dockerfile_sha256"],
|
||||
"LAB V1 component Dockerfile SHA-256",
|
||||
),
|
||||
build_method=_string(
|
||||
row["build_method"],
|
||||
"LAB V1 component image build method",
|
||||
),
|
||||
installer_sha256=_string(
|
||||
row["installer_sha256"],
|
||||
"LAB V1 component image installer SHA-256",
|
||||
),
|
||||
shared_adapter_sha256=_string(
|
||||
row["shared_adapter_sha256"],
|
||||
"LAB V1 shared adapter SHA-256",
|
||||
@@ -694,6 +745,7 @@ def _verify_release_covers_runner_installation(
|
||||
for build in image_builds
|
||||
for digest in (
|
||||
build.dockerfile_sha256,
|
||||
build.installer_sha256,
|
||||
build.shared_adapter_sha256,
|
||||
build.component_adapter_sha256,
|
||||
build.seal_sha256,
|
||||
@@ -969,6 +1021,7 @@ def _utc_now() -> str:
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_METHOD",
|
||||
"PORTABLE_LAB_V1_COMPONENT_IMAGE_BUILD_SEAL_SCHEMA",
|
||||
"PORTABLE_LAB_V1_ADAPTER_ID",
|
||||
"PORTABLE_LAB_V1_SETUP_ID",
|
||||
|
||||
Reference in New Issue
Block a user