e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
217 lines
7.4 KiB
Python
217 lines
7.4 KiB
Python
# Copyright 2026 Alibaba Group Holding Ltd.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Optional
|
|
|
|
from opensandbox_server.api.schema import ImageSpec, PlatformSpec, Sandbox, SandboxStatus
|
|
from opensandbox_server.extensions import extract_extensions_from_mapping
|
|
from opensandbox_server.services.constants import SANDBOX_ID_LABEL, SANDBOX_SNAPSHOT_ID_LABEL
|
|
|
|
|
|
def _is_opensandbox_label(label_key: str) -> bool:
|
|
return label_key.split("/", 1)[0] == "opensandbox.io"
|
|
|
|
|
|
def _build_sandbox_from_workload(workload: Any, workload_provider: Any) -> Sandbox:
|
|
if isinstance(workload, dict):
|
|
metadata = workload.get("metadata", {})
|
|
spec = workload.get("spec", {})
|
|
labels = metadata.get("labels", {})
|
|
annotations = metadata.get("annotations", {})
|
|
creation_timestamp = metadata.get("creationTimestamp")
|
|
else:
|
|
metadata = workload.metadata
|
|
spec = workload.spec
|
|
labels = metadata.labels or {}
|
|
annotations = metadata.annotations or {}
|
|
creation_timestamp = metadata.creation_timestamp
|
|
|
|
sandbox_id = labels.get(SANDBOX_ID_LABEL, "")
|
|
snapshot_id = labels.get(SANDBOX_SNAPSHOT_ID_LABEL)
|
|
expires_at = workload_provider.get_expiration(workload)
|
|
status_info = workload_provider.get_status(workload)
|
|
|
|
user_metadata = {
|
|
k: v for k, v in labels.items() if not _is_opensandbox_label(k)
|
|
}
|
|
|
|
image_uri = ""
|
|
entrypoint = []
|
|
if isinstance(workload, dict):
|
|
template = spec.get("template") or spec.get("podTemplate") or {}
|
|
pod_spec = template.get("spec", {})
|
|
containers = pod_spec.get("containers", [])
|
|
if containers:
|
|
container = containers[0]
|
|
image_uri = container.get("image", "")
|
|
entrypoint = container.get("command", [])
|
|
elif hasattr(spec, "containers") and spec.containers:
|
|
container = spec.containers[0]
|
|
image_uri = container.image or ""
|
|
entrypoint = container.command or []
|
|
|
|
image_spec = None
|
|
if not snapshot_id:
|
|
image_spec = ImageSpec(uri=image_uri) if image_uri else ImageSpec(uri="unknown")
|
|
platform_spec = _extract_platform_from_workload(workload)
|
|
return Sandbox(
|
|
id=sandbox_id,
|
|
status=SandboxStatus(
|
|
state=status_info["state"],
|
|
reason=status_info["reason"],
|
|
message=status_info["message"],
|
|
last_transition_at=status_info["last_transition_at"],
|
|
),
|
|
created_at=creation_timestamp,
|
|
expires_at=expires_at,
|
|
metadata=user_metadata if user_metadata else None,
|
|
extensions=extract_extensions_from_mapping(annotations),
|
|
image=image_spec,
|
|
snapshotId=snapshot_id,
|
|
entrypoint=entrypoint,
|
|
platform=platform_spec,
|
|
)
|
|
|
|
|
|
def _extract_platform_from_workload(workload: Any) -> Optional[PlatformSpec]:
|
|
if isinstance(workload, dict):
|
|
spec = workload.get("spec") or {}
|
|
template = spec.get("template") or {}
|
|
pod_template = spec.get("podTemplate") or {}
|
|
pod_spec = (
|
|
(template.get("spec") if isinstance(template, dict) else None)
|
|
or (pod_template.get("spec") if isinstance(pod_template, dict) else None)
|
|
or {}
|
|
)
|
|
else:
|
|
spec = getattr(workload, "spec", None)
|
|
template = getattr(spec, "template", None)
|
|
pod_template = getattr(spec, "pod_template", None)
|
|
pod_spec = (
|
|
getattr(template, "spec", None)
|
|
or getattr(pod_template, "spec", None)
|
|
or {}
|
|
)
|
|
|
|
node_selector = (
|
|
pod_spec.get("nodeSelector", {})
|
|
if isinstance(pod_spec, dict)
|
|
else getattr(pod_spec, "node_selector", {}) or {}
|
|
)
|
|
if not isinstance(node_selector, dict):
|
|
return None
|
|
|
|
os_value = node_selector.get("kubernetes.io/os")
|
|
arch_value = node_selector.get("kubernetes.io/arch")
|
|
os_constraint = os_value if isinstance(os_value, str) and os_value else None
|
|
arch_constraint = arch_value if isinstance(arch_value, str) and arch_value else None
|
|
|
|
affinity = (
|
|
pod_spec.get("affinity")
|
|
if isinstance(pod_spec, dict)
|
|
else getattr(pod_spec, "affinity", None)
|
|
)
|
|
if os_constraint is None:
|
|
os_constraint = _extract_platform_value_from_affinity(
|
|
affinity,
|
|
"kubernetes.io/os",
|
|
)
|
|
if arch_constraint is None:
|
|
arch_constraint = _extract_platform_value_from_affinity(
|
|
affinity,
|
|
"kubernetes.io/arch",
|
|
)
|
|
|
|
if os_constraint and arch_constraint:
|
|
return PlatformSpec(os=os_constraint, arch=arch_constraint)
|
|
return None
|
|
|
|
|
|
def _extract_platform_value_from_affinity(
|
|
affinity: Any,
|
|
key: str,
|
|
) -> Optional[str]:
|
|
if affinity is None:
|
|
return None
|
|
node_affinity = (
|
|
affinity.get("nodeAffinity")
|
|
if isinstance(affinity, dict)
|
|
else getattr(affinity, "node_affinity", None)
|
|
)
|
|
if node_affinity is None:
|
|
return None
|
|
required = (
|
|
node_affinity.get("requiredDuringSchedulingIgnoredDuringExecution")
|
|
if isinstance(node_affinity, dict)
|
|
else getattr(
|
|
node_affinity,
|
|
"required_during_scheduling_ignored_during_execution",
|
|
None,
|
|
)
|
|
)
|
|
if required is None:
|
|
return None
|
|
terms = (
|
|
required.get("nodeSelectorTerms", [])
|
|
if isinstance(required, dict)
|
|
else getattr(required, "node_selector_terms", []) or []
|
|
)
|
|
if not isinstance(terms, list) or not terms:
|
|
return None
|
|
|
|
inferred: Optional[str] = None
|
|
for term in terms:
|
|
expressions = (
|
|
term.get("matchExpressions", [])
|
|
if isinstance(term, dict)
|
|
else getattr(term, "match_expressions", []) or []
|
|
)
|
|
if not isinstance(expressions, list):
|
|
return None
|
|
term_value: Optional[str] = None
|
|
for expr in expressions:
|
|
expr_key = (
|
|
expr.get("key")
|
|
if isinstance(expr, dict)
|
|
else getattr(expr, "key", None)
|
|
)
|
|
if expr_key != key:
|
|
continue
|
|
operator = (
|
|
expr.get("operator")
|
|
if isinstance(expr, dict)
|
|
else getattr(expr, "operator", None)
|
|
)
|
|
values = (
|
|
expr.get("values", [])
|
|
if isinstance(expr, dict)
|
|
else getattr(expr, "values", []) or []
|
|
)
|
|
if operator != "In" or not isinstance(values, list) or len(values) != 1:
|
|
return None
|
|
value = values[0]
|
|
if not isinstance(value, str) or not value:
|
|
return None
|
|
term_value = value
|
|
break
|
|
if term_value is None:
|
|
return None
|
|
if inferred is None:
|
|
inferred = term_value
|
|
elif inferred != term_value:
|
|
return None
|
|
return inferred
|