Commit 6a9fa308 authored by Konstantinos Togias's avatar Konstantinos Togias
Browse files

Import TF-SDK developer contract updates (2026-10-02)

parent aab26f47
Loading
Loading
Loading
Loading
+30 −21
Changes for src/openop_tfsdk/contract/adapters/kubernetes.py: 30 added lines, 21 removed lines.
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ import json
import subprocess
from typing import Optional

from openop_tfsdk.contract.errors import DomainExecutionFailed, ValidationError
from openop_tfsdk.contract.execution_models import (
    CapabilityLifecycleRequest,
    CapabilityLifecycleResult,
@@ -19,6 +20,9 @@ from openop_tfsdk.contract.execution_models import (
    RuntimeState,
)
from openop_tfsdk.contract.models import SupportedAction
from openop_tfsdk.logger import setup_logger

log = setup_logger(__name__)

LIFECYCLE_CAPABILITY_KINDS = frozenset({"workload"})

@@ -33,7 +37,7 @@ RUNTIME_KINDS = ["helm"]


class KubernetesCapabilityAdapter:
    """Declares the kubernetes execution surface. Performs no backend call."""
    """Declares the kubernetes execution surface and executes it through helm."""

    adapter_type = "kubernetes"

@@ -48,6 +52,8 @@ class KubernetesCapabilityAdapter:

    def activate(self, request: CapabilityLifecycleRequest) -> CapabilityLifecycleResult:
        unit = request.deployment_unit
        if unit is None or not unit.artifact_ref:
            raise ValidationError("activate needs a deployment_unit with an artifact_ref")
        release_name = unit.deployment_unit_ref
        namespace = self._infrastructure.get("namespace", "default")

@@ -59,6 +65,8 @@ class KubernetesCapabilityAdapter:
                unit.artifact_ref,
                "--namespace",
                namespace,
                "--create-namespace",
                "--atomic",
                "--wait",
            ],
            capture_output=True,
@@ -66,12 +74,8 @@ class KubernetesCapabilityAdapter:
        )

        if result.returncode != 0:
            return CapabilityLifecycleResult(
                operation_id=request.operation_id,
                capability_kind=request.capability_kind,
                state=LifecycleState.FAILED,
                details={"stderr": result.stderr},
            )
            log.error("helm install failed: %s", result.stderr)
            raise DomainExecutionFailed("helm install failed")

        return CapabilityLifecycleResult(
            operation_id=request.operation_id,
@@ -81,6 +85,8 @@ class KubernetesCapabilityAdapter:
        )

    def read_runtime_state(self, request: CapabilityRuntimeStateRequest) -> CapabilityRuntimeState:
        if not request.external_id:
            raise ValidationError("read_runtime_state needs the external_id returned by activate")
        release_name = request.external_id
        namespace = self._infrastructure.get("namespace", "default")

@@ -99,15 +105,20 @@ class KubernetesCapabilityAdapter:
        )

        if result.returncode != 0:
            log.warning("helm status failed: %s", result.stderr)
            return CapabilityRuntimeState(
                external_id=release_name,
                capability_kind=request.capability_kind,
                state=RuntimeState.UNKNOWN,
                details={"stderr": result.stderr},
            )

        helm_status = json.loads(result.stdout)["info"]["status"]
        state = RuntimeState.ACTIVE if helm_status == "deployed" else RuntimeState.DEGRADED
        if helm_status == "deployed":
            state = RuntimeState.ACTIVE
        elif helm_status == "failed":
            state = RuntimeState.FAILED
        else:
            state = RuntimeState.DEGRADED

        return CapabilityRuntimeState(
            external_id=release_name,
@@ -117,6 +128,8 @@ class KubernetesCapabilityAdapter:
        )

    def terminate(self, request: CapabilityLifecycleRequest) -> CapabilityLifecycleResult:
        if not request.external_id:
            raise ValidationError("terminate needs the external_id returned by activate")
        release_name = request.external_id
        namespace = self._infrastructure.get("namespace", "default")

@@ -134,12 +147,8 @@ class KubernetesCapabilityAdapter:
        )

        if result.returncode != 0:
            return CapabilityLifecycleResult(
                operation_id=request.operation_id,
                capability_kind=request.capability_kind,
                state=LifecycleState.FAILED,
                details={"stderr": result.stderr},
            )
            log.error("helm uninstall failed: %s", result.stderr)
            raise DomainExecutionFailed("helm uninstall failed")

        return CapabilityLifecycleResult(
            operation_id=request.operation_id,
@@ -150,6 +159,10 @@ class KubernetesCapabilityAdapter:

    def update(self, request: CapabilityLifecycleRequest) -> CapabilityLifecycleResult:
        unit = request.deployment_unit
        if not request.external_id:
            raise ValidationError("update needs the external_id returned by activate")
        if unit is None or not unit.artifact_ref:
            raise ValidationError("update needs a deployment_unit with an artifact_ref")
        release_name = request.external_id
        namespace = self._infrastructure.get("namespace", "default")

@@ -168,12 +181,8 @@ class KubernetesCapabilityAdapter:
        )

        if result.returncode != 0:
            return CapabilityLifecycleResult(
                operation_id=request.operation_id,
                capability_kind=request.capability_kind,
                state=LifecycleState.FAILED,
                details={"stderr": result.stderr},
            )
            log.error("helm upgrade failed: %s", result.stderr)
            raise DomainExecutionFailed("helm upgrade failed")

        return CapabilityLifecycleResult(
            operation_id=request.operation_id,
+10 −0
Changes for src/openop_tfsdk/contract/errors.py: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -39,3 +39,13 @@ class InvalidTargetRef(TFSDKError):
class UnknownAdapterKind(TFSDKError):
    error_code = "UNKNOWN_ADAPTER_KIND"
    retryable = False


class ValidationError(TFSDKError):
    error_code = "VALIDATION_ERROR"
    retryable = False


class DomainExecutionFailed(TFSDKError):
    error_code = "DOMAIN_EXECUTION_FAILED"
    retryable = False
+2 −4
Changes for src/openop_tfsdk/contract/topology.py: 2 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -16,13 +16,13 @@ from typing import List, Optional, Tuple
import yaml

from openop_tfsdk.contract.adapters import capability_surface
from openop_tfsdk.contract.errors import InvalidTargetRef
from openop_tfsdk.contract.interfaces import TopologyDiscoveryAdapter
from openop_tfsdk.contract.models import (
    Capability,
    ControlPathBinding,
    Domain,
    State,
    SupportedAction,
    Zone,
)

@@ -111,9 +111,7 @@ class BootstrapTopology(TopologyDiscoveryAdapter):
        # for its adapter kind and configuration, without building an adapter.
        control_path = self._control_paths.get(raw["control_path_ref"])
        if control_path is None:
            supported_actions: List[SupportedAction] = []
            supported_runtime_kinds: List[str] = []
        else:
            raise InvalidTargetRef(raw["control_path_ref"])
        adapter_type, infrastructure = control_path
        supported_actions, supported_runtime_kinds = capability_surface(
            adapter_type, capability_kind, infrastructure