opensandbox-server 0.2.2__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/.gitignore +17 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/PKG-INFO +5 -2
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/__init__.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/api/__init__.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/api/devops.py +37 -36
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/api/lifecycle.py +42 -17
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/api/metrics.py +4 -9
- opensandbox_server-1.1.0/opensandbox_server/api/network_policy.py +60 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/api/pool.py +1 -5
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/api/proxy.py +238 -66
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/api/schema.py +274 -2
- opensandbox_server-1.1.0/opensandbox_server/api/templates.py +199 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/cli.py +50 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/config.py +365 -82
- opensandbox_server-1.1.0/opensandbox_server/constants.py +18 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/examples/__init__.py +1 -1
- opensandbox_server-1.1.0/opensandbox_server/examples/e2e.batchsandbox-template.yaml +33 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/examples/example.config.k8s.toml +31 -10
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/examples/example.config.k8s.zh.toml +29 -10
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/examples/example.config.toml +28 -3
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/examples/example.config.zh.toml +25 -3
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/extensions/__init__.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/extensions/codec.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/extensions/keys.py +1 -3
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/extensions/validation.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/__init__.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/otel/__init__.py +4 -2
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/otel/metrics.py +78 -10
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/renew_intent/__init__.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/renew_intent/constants.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/renew_intent/consumer.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/renew_intent/controller.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/renew_intent/intent.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/renew_intent/logutil.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/renew_intent/proxy_renew.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/renew_intent/redis_client.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/integrations/renew_intent/runner.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/logging_config.py +1 -3
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/main.py +88 -14
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/middleware/__init__.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/middleware/auth.py +3 -3
- opensandbox_server-1.1.0/opensandbox_server/middleware/date_header.py +43 -0
- opensandbox_server-1.1.0/opensandbox_server/middleware/http_metrics.py +86 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/middleware/request_id.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/repositories/__init__.py +1 -1
- opensandbox_server-1.1.0/opensandbox_server/repositories/snapshots/__init__.py +57 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/repositories/snapshots/factory.py +32 -1
- opensandbox_server-1.1.0/opensandbox_server/repositories/snapshots/migrate.py +255 -0
- opensandbox_server-1.1.0/opensandbox_server/repositories/snapshots/postgresql.py +338 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/repositories/snapshots/sqlite.py +13 -20
- {opensandbox_server-0.2.2/opensandbox_server/repositories/snapshots → opensandbox_server-1.1.0/opensandbox_server/repositories/templates}/__init__.py +12 -6
- opensandbox_server-1.1.0/opensandbox_server/repositories/templates/factory.py +96 -0
- opensandbox_server-1.1.0/opensandbox_server/repositories/templates/postgresql.py +280 -0
- opensandbox_server-1.1.0/opensandbox_server/repositories/templates/sqlite.py +266 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/__init__.py +1 -1
- opensandbox_server-1.1.0/opensandbox_server/services/composite_service.py +154 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/constants.py +37 -2
- opensandbox_server-1.1.0/opensandbox_server/services/diagnostics.py +85 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/__init__.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/container_ops.py +49 -33
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/docker_diagnostics.py +93 -9
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/docker_service.py +277 -151
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/metadata.py +3 -16
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/networking.py +122 -55
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/ossfs_mixin.py +11 -47
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/port_allocator.py +65 -11
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/runtime.py +126 -12
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/snapshot_runtime.py +31 -17
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/volumes.py +9 -31
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/docker/windows_profile.py +3 -3
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/endpoint_auth.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/extension_service.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/factory.py +12 -7
- opensandbox_server-1.1.0/opensandbox_server/services/fast_sandbox/__init__.py +17 -0
- opensandbox_server-1.1.0/opensandbox_server/services/fast_sandbox/cr_mapping.py +82 -0
- opensandbox_server-1.1.0/opensandbox_server/services/fast_sandbox/cr_reader.py +100 -0
- opensandbox_server-1.1.0/opensandbox_server/services/fast_sandbox/create_mapping.py +314 -0
- opensandbox_server-1.1.0/opensandbox_server/services/fast_sandbox/endpoint.py +91 -0
- opensandbox_server-1.1.0/opensandbox_server/services/fast_sandbox/fastpath_client.py +443 -0
- opensandbox_server-1.1.0/opensandbox_server/services/fast_sandbox/network_policy.py +65 -0
- opensandbox_server-1.1.0/opensandbox_server/services/fast_sandbox/service.py +694 -0
- opensandbox_server-1.1.0/opensandbox_server/services/fast_sandbox/snapshot_runtime.py +350 -0
- opensandbox_server-1.1.0/opensandbox_server/services/fast_sandbox/status_mapping.py +97 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/helpers.py +21 -9
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/__init__.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/agent_sandbox_provider.py +97 -42
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/agent_sandbox_template.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/batchsandbox_provider.py +176 -82
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/batchsandbox_template.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/client.py +115 -31
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/create_helpers.py +32 -19
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/egress_helper.py +54 -25
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/endpoint_resolver.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/error_helpers.py +31 -2
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/image_pull_secret_helper.py +25 -2
- opensandbox_server-1.1.0/opensandbox_server/services/k8s/informer.py +307 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/k8s_diagnostics.py +150 -15
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/kubernetes_service.py +132 -141
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/label_selector.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/list_helpers.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/pool_service.py +1 -2
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/provider_common.py +10 -2
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/provider_factory.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/rate_limiter.py +2 -7
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/security_context.py +1 -1
- opensandbox_server-1.1.0/opensandbox_server/services/k8s/snapshot_runtime.py +688 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/status_helpers.py +28 -2
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/template_manager.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/volume_helper.py +12 -26
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/windows_profile.py +5 -5
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/workload_access.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/workload_mapper.py +123 -2
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/k8s/workload_provider.py +29 -13
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/runtime_resolver.py +13 -16
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/sandbox_service.py +45 -2
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/signing.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/snapshot_models.py +16 -5
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/snapshot_repository.py +6 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/snapshot_restore.py +23 -14
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/snapshot_runtime.py +67 -5
- opensandbox_server-1.1.0/opensandbox_server/services/snapshot_runtime_factory.py +196 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/snapshot_service.py +307 -48
- opensandbox_server-1.1.0/opensandbox_server/services/templates/__init__.py +13 -0
- opensandbox_server-1.1.0/opensandbox_server/services/templates/template_models.py +84 -0
- opensandbox_server-1.1.0/opensandbox_server/services/templates/template_service.py +533 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/validators.py +6 -79
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/services/windows_common.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/startup_guard.py +3 -4
- opensandbox_server-1.1.0/opensandbox_server/tenants/__init__.py +156 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/tenants/context.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/tenants/file_provider.py +11 -4
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/tenants/http_provider.py +12 -6
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/tenants/models.py +1 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/tenants/provider.py +9 -1
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/pyproject.toml +19 -4
- opensandbox_server-0.2.2/opensandbox_server/examples/e2e.batchsandbox-template.yaml +0 -17
- opensandbox_server-0.2.2/opensandbox_server/services/k8s/informer.py +0 -242
- opensandbox_server-0.2.2/opensandbox_server/services/k8s/snapshot_runtime.py +0 -326
- opensandbox_server-0.2.2/opensandbox_server/services/snapshot_runtime_factory.py +0 -63
- opensandbox_server-0.2.2/opensandbox_server/tenants/__init__.py +0 -65
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/LICENSE +0 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/README.md +0 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/examples/.gitkeep +0 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/examples/example.batchsandbox-template.yaml +0 -0
- {opensandbox_server-0.2.2 → opensandbox_server-1.1.0}/opensandbox_server/py.typed +0 -0
|
@@ -269,6 +269,19 @@ nbdist/
|
|
|
269
269
|
generated/
|
|
270
270
|
**/generated/**
|
|
271
271
|
|
|
272
|
+
# Fast Sandbox FastPath gRPC stubs are committed intentionally (deterministic protoc
|
|
273
|
+
# output; the server package needs them at install time, so they are checked
|
|
274
|
+
# in rather than regenerated).
|
|
275
|
+
!server/opensandbox_server/services/fast_sandbox/generated/
|
|
276
|
+
!server/opensandbox_server/services/fast_sandbox/generated/**
|
|
277
|
+
server/opensandbox_server/services/fast_sandbox/generated/__pycache__/
|
|
278
|
+
|
|
279
|
+
# execd eBPF kernel BTF dumps (bpf2go build-time only, never committed —
|
|
280
|
+
# see components/execd/pkg/ebpf/prog/audit.bpf.c; regenerate with:
|
|
281
|
+
# bpftool btf dump file /sys/kernel/btf/vmlinux format c > .../vmlinux.h)
|
|
282
|
+
components/execd/pkg/ebpf/prog/vmlinux.h
|
|
283
|
+
components/execd/pkg/ebpf/prog/vmlinux_*.h
|
|
284
|
+
|
|
272
285
|
# gVisor runtime binaries (downloaded dynamically)
|
|
273
286
|
kubernetes/test/kind/gvisor/runsc
|
|
274
287
|
kubernetes/test/kind/gvisor/containerd-shim-runsc-v1
|
|
@@ -277,3 +290,7 @@ obj/
|
|
|
277
290
|
|
|
278
291
|
.qoder/
|
|
279
292
|
/.vs
|
|
293
|
+
|
|
294
|
+
# fast-sandbox pinned source checkout (materialized by
|
|
295
|
+
# manifests/release/build-fast-sandbox.sh from manifests/third-party/fast-sandbox.commit)
|
|
296
|
+
.fast-sandbox/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: opensandbox-server
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: FastAPI control plane for OpenSandbox that manages sandbox lifecycle on Docker (ready) and Kubernetes (planned) runtimes.
|
|
5
5
|
Project-URL: Homepage, https://github.com/opensandbox-group/OpenSandbox
|
|
6
6
|
Project-URL: Repository, https://github.com/opensandbox-group/OpenSandbox
|
|
@@ -24,11 +24,14 @@ Classifier: Typing :: Typed
|
|
|
24
24
|
Requires-Python: >=3.10
|
|
25
25
|
Requires-Dist: docker
|
|
26
26
|
Requires-Dist: fastapi>=0.137.0
|
|
27
|
+
Requires-Dist: grpcio>=1.83.0
|
|
27
28
|
Requires-Dist: httpx[socks]
|
|
28
29
|
Requires-Dist: kubernetes
|
|
29
30
|
Requires-Dist: opentelemetry-api
|
|
30
31
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http
|
|
31
32
|
Requires-Dist: opentelemetry-sdk
|
|
33
|
+
Requires-Dist: protobuf>=7.35.1
|
|
34
|
+
Requires-Dist: psycopg[binary,pool]<4,>=3.2
|
|
32
35
|
Requires-Dist: pydantic
|
|
33
36
|
Requires-Dist: pydantic-settings>=2.14.2
|
|
34
37
|
Requires-Dist: python-multipart>=0.0.31
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2026
|
|
1
|
+
# Copyright 2026 The OpenSandbox Authors
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -15,10 +15,9 @@
|
|
|
15
15
|
"""
|
|
16
16
|
API routes for OpenSandbox DevOps diagnostics.
|
|
17
17
|
|
|
18
|
-
Requests that include
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
plain-text behavior for legacy humans, agents, and CLI clients.
|
|
18
|
+
Requests that include ``scope`` target the stable Diagnostics API and return an
|
|
19
|
+
inline JSON descriptor. Requests without ``scope`` preserve the deprecated
|
|
20
|
+
DevOps plain-text behavior for legacy humans, agents, and CLI clients.
|
|
22
21
|
"""
|
|
23
22
|
|
|
24
23
|
import logging
|
|
@@ -28,19 +27,29 @@ from fastapi import APIRouter, HTTPException, Query, status
|
|
|
28
27
|
from fastapi.responses import JSONResponse, PlainTextResponse
|
|
29
28
|
|
|
30
29
|
from opensandbox_server.api.lifecycle import sandbox_service
|
|
30
|
+
from opensandbox_server.services.diagnostics import DiagnosticResult
|
|
31
31
|
|
|
32
32
|
logger = logging.getLogger(__name__)
|
|
33
33
|
router = APIRouter(tags=["DevOps"])
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
def _diagnostic_inline_response(
|
|
37
|
+
result: DiagnosticResult,
|
|
38
|
+
) -> JSONResponse:
|
|
39
|
+
"""Build a Diagnostics API descriptor for inline plain-text content."""
|
|
40
|
+
payload: dict[str, object] = {
|
|
41
|
+
"sandboxId": result.sandbox_id,
|
|
42
|
+
"kind": result.kind,
|
|
43
|
+
"scope": result.scope,
|
|
44
|
+
"delivery": "inline",
|
|
45
|
+
"contentType": "text/plain; charset=utf-8",
|
|
46
|
+
"content": result.content,
|
|
47
|
+
"contentLength": len(result.content.encode("utf-8")),
|
|
48
|
+
"truncated": result.truncated,
|
|
49
|
+
}
|
|
50
|
+
if result.warnings:
|
|
51
|
+
payload["warnings"] = list(result.warnings)
|
|
52
|
+
return JSONResponse(status_code=status.HTTP_200_OK, content=payload)
|
|
44
53
|
|
|
45
54
|
|
|
46
55
|
def _deprecated_plain_text_response(content: str) -> PlainTextResponse:
|
|
@@ -56,13 +65,10 @@ def _deprecated_plain_text_response(content: str) -> PlainTextResponse:
|
|
|
56
65
|
status_code=status.HTTP_200_OK,
|
|
57
66
|
responses={
|
|
58
67
|
200: {
|
|
59
|
-
"description": "
|
|
60
|
-
"content": {"text/plain": {}},
|
|
61
|
-
},
|
|
62
|
-
501: {
|
|
63
|
-
"description": "Stable Diagnostics API is not implemented by this server",
|
|
64
|
-
"content": {"application/json": {}},
|
|
68
|
+
"description": "Stable JSON descriptor, or deprecated text when scope is omitted",
|
|
69
|
+
"content": {"application/json": {}, "text/plain": {}},
|
|
65
70
|
},
|
|
71
|
+
400: {"description": "Unsupported diagnostics scope"},
|
|
66
72
|
404: {"description": "Sandbox not found"},
|
|
67
73
|
},
|
|
68
74
|
)
|
|
@@ -96,10 +102,9 @@ def get_sandbox_logs(
|
|
|
96
102
|
) -> JSONResponse | PlainTextResponse:
|
|
97
103
|
"""Retrieve diagnostic logs for a sandbox."""
|
|
98
104
|
if scope is not None:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
105
|
+
result = sandbox_service.get_sandbox_log_diagnostics(sandbox_id, scope)
|
|
106
|
+
return _diagnostic_inline_response(result)
|
|
107
|
+
text = sandbox_service.get_sandbox_logs(sandbox_id, tail=tail, since=since, container=container)
|
|
103
108
|
return _deprecated_plain_text_response(text)
|
|
104
109
|
|
|
105
110
|
|
|
@@ -125,13 +130,10 @@ def get_sandbox_inspect(sandbox_id: str) -> PlainTextResponse:
|
|
|
125
130
|
status_code=status.HTTP_200_OK,
|
|
126
131
|
responses={
|
|
127
132
|
200: {
|
|
128
|
-
"description": "
|
|
129
|
-
"content": {"text/plain": {}},
|
|
130
|
-
},
|
|
131
|
-
501: {
|
|
132
|
-
"description": "Stable Diagnostics API is not implemented by this server",
|
|
133
|
-
"content": {"application/json": {}},
|
|
133
|
+
"description": "Stable JSON descriptor, or deprecated text when scope is omitted",
|
|
134
|
+
"content": {"application/json": {}, "text/plain": {}},
|
|
134
135
|
},
|
|
136
|
+
400: {"description": "Unsupported diagnostics scope"},
|
|
135
137
|
404: {"description": "Sandbox not found"},
|
|
136
138
|
},
|
|
137
139
|
)
|
|
@@ -151,7 +153,8 @@ def get_sandbox_events(
|
|
|
151
153
|
) -> JSONResponse | PlainTextResponse:
|
|
152
154
|
"""Retrieve diagnostic events for a sandbox."""
|
|
153
155
|
if scope is not None:
|
|
154
|
-
|
|
156
|
+
result = sandbox_service.get_sandbox_event_diagnostics(sandbox_id, scope)
|
|
157
|
+
return _diagnostic_inline_response(result)
|
|
155
158
|
text = sandbox_service.get_sandbox_events(sandbox_id, limit=limit)
|
|
156
159
|
return _deprecated_plain_text_response(text)
|
|
157
160
|
|
|
@@ -189,10 +192,9 @@ def get_sandbox_diagnostics_summary(
|
|
|
189
192
|
except HTTPException:
|
|
190
193
|
raise
|
|
191
194
|
except Exception:
|
|
192
|
-
logger.exception("Failed to collect sandbox inspect diagnostics for
|
|
195
|
+
logger.exception(f"Failed to collect sandbox inspect diagnostics for {sandbox_id}")
|
|
193
196
|
sections.append("[error] Failed to collect inspect diagnostics.")
|
|
194
197
|
|
|
195
|
-
# Events
|
|
196
198
|
sections.append("")
|
|
197
199
|
sections.append("-" * 40)
|
|
198
200
|
sections.append("EVENTS")
|
|
@@ -202,20 +204,19 @@ def get_sandbox_diagnostics_summary(
|
|
|
202
204
|
except HTTPException:
|
|
203
205
|
raise
|
|
204
206
|
except Exception:
|
|
205
|
-
logger.exception("Failed to collect sandbox event diagnostics for
|
|
207
|
+
logger.exception(f"Failed to collect sandbox event diagnostics for {sandbox_id}")
|
|
206
208
|
sections.append("[error] Failed to collect event diagnostics.")
|
|
207
209
|
|
|
208
|
-
# Logs
|
|
209
210
|
sections.append("")
|
|
210
211
|
sections.append("-" * 40)
|
|
211
|
-
sections.append("LOGS (last {} lines)"
|
|
212
|
+
sections.append(f"LOGS (last {tail} lines)")
|
|
212
213
|
sections.append("-" * 40)
|
|
213
214
|
try:
|
|
214
215
|
sections.append(sandbox_service.get_sandbox_logs(sandbox_id, tail=tail))
|
|
215
216
|
except HTTPException:
|
|
216
217
|
raise
|
|
217
218
|
except Exception:
|
|
218
|
-
logger.exception("Failed to collect sandbox log diagnostics for
|
|
219
|
+
logger.exception(f"Failed to collect sandbox log diagnostics for {sandbox_id}")
|
|
219
220
|
sections.append("[error] Failed to collect log diagnostics.")
|
|
220
221
|
|
|
221
222
|
return PlainTextResponse(content="\n".join(sections) + "\n")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2025
|
|
1
|
+
# Copyright 2025 The OpenSandbox Authors
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -45,16 +45,22 @@ from opensandbox_server.api.schema import (
|
|
|
45
45
|
Snapshot,
|
|
46
46
|
SnapshotFilter,
|
|
47
47
|
)
|
|
48
|
-
from opensandbox_server.services.constants import
|
|
48
|
+
from opensandbox_server.services.constants import (
|
|
49
|
+
OPEN_SANDBOX_INGRESS_HEADER,
|
|
50
|
+
OPEN_SANDBOX_ORIGIN_HEADER,
|
|
51
|
+
SANDBOX_ORIGIN_TEMPLATE,
|
|
52
|
+
SandboxErrorCodes,
|
|
53
|
+
)
|
|
49
54
|
from opensandbox_server.services.factory import create_sandbox_service
|
|
55
|
+
from opensandbox_server.services.snapshot_restore import resolve_sandbox_image_from_request
|
|
50
56
|
from opensandbox_server.services.snapshot_service import create_snapshot_service
|
|
51
57
|
|
|
52
|
-
# Initialize router
|
|
53
58
|
router = APIRouter(tags=["Sandboxes"])
|
|
54
59
|
|
|
55
|
-
# Initialize service based on configuration from config.toml (defaults to docker)
|
|
56
60
|
sandbox_service = create_sandbox_service()
|
|
57
61
|
snapshot_service = create_snapshot_service(sandbox_service)
|
|
62
|
+
# React to snapshot status changes so rows converge without waiting on reads
|
|
63
|
+
snapshot_service.start_background_sync()
|
|
58
64
|
|
|
59
65
|
|
|
60
66
|
# ============================================================================
|
|
@@ -67,10 +73,21 @@ snapshot_service = create_snapshot_service(sandbox_service)
|
|
|
67
73
|
response_model_exclude_none=True,
|
|
68
74
|
status_code=status.HTTP_202_ACCEPTED,
|
|
69
75
|
responses={
|
|
70
|
-
202: {"description": "Sandbox
|
|
76
|
+
202: {"description": "Sandbox created and provisioned successfully"},
|
|
71
77
|
400: {"model": ErrorResponse, "description": "The request was invalid or malformed"},
|
|
72
78
|
401: {"model": ErrorResponse, "description": "Authentication credentials are missing or invalid"},
|
|
79
|
+
403: {"model": ErrorResponse, "description": "Namespace ResourceQuota exhausted — the sandbox was not admitted (KUBERNETES::QUOTA_EXCEEDED)"},
|
|
73
80
|
409: {"model": ErrorResponse, "description": "The operation conflicts with the current state"},
|
|
81
|
+
429: {
|
|
82
|
+
"model": ErrorResponse,
|
|
83
|
+
"description": "Pool capacity remained unavailable before the acquisition timeout",
|
|
84
|
+
"headers": {
|
|
85
|
+
"Retry-After": {
|
|
86
|
+
"description": "Suggested delay in seconds before retrying",
|
|
87
|
+
"schema": {"type": "integer", "minimum": 1},
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
},
|
|
74
91
|
500: {"model": ErrorResponse, "description": "An unexpected server error occurred"},
|
|
75
92
|
},
|
|
76
93
|
)
|
|
@@ -96,10 +113,13 @@ async def create_sandbox(
|
|
|
96
113
|
HTTPException: If sandbox creation scheduling fails
|
|
97
114
|
"""
|
|
98
115
|
validate_extensions(request.extensions)
|
|
116
|
+
# Resolve snapshotId before backend routing: the owning backend recorded
|
|
117
|
+
# on the snapshot row selects the fsb vs pod backend for restores.
|
|
118
|
+
if not (request.template_id or "").strip():
|
|
119
|
+
request = await resolve_sandbox_image_from_request(request)
|
|
99
120
|
return await sandbox_service.create_sandbox(request)
|
|
100
121
|
|
|
101
122
|
|
|
102
|
-
# Search endpoint
|
|
103
123
|
@router.get(
|
|
104
124
|
"/sandboxes",
|
|
105
125
|
response_model=ListSandboxesResponse,
|
|
@@ -134,12 +154,10 @@ def list_sandboxes(
|
|
|
134
154
|
Returns:
|
|
135
155
|
ListSandboxesResponse: Paginated list of sandboxes
|
|
136
156
|
"""
|
|
137
|
-
# Parse metadata query string into dictionary
|
|
138
157
|
metadata_dict = {}
|
|
139
158
|
if metadata:
|
|
140
159
|
from urllib.parse import parse_qsl
|
|
141
160
|
try:
|
|
142
|
-
# Parse query string format: key=value&key2=value2
|
|
143
161
|
# strict_parsing=True rejects malformed segments like "a=1&broken"
|
|
144
162
|
parsed = parse_qsl(metadata, keep_blank_values=True, strict_parsing=True)
|
|
145
163
|
metadata_dict = dict(parsed)
|
|
@@ -150,7 +168,6 @@ def list_sandboxes(
|
|
|
150
168
|
detail={"code": "INVALID_METADATA_FORMAT", "message": f"Invalid metadata format: {str(e)}"}
|
|
151
169
|
)
|
|
152
170
|
|
|
153
|
-
# Construct request object
|
|
154
171
|
request = ListSandboxesRequest(
|
|
155
172
|
filter=SandboxFilter(state=state, metadata=metadata_dict if metadata_dict else None),
|
|
156
173
|
pagination=PaginationRequest(page=page, pageSize=page_size)
|
|
@@ -158,9 +175,8 @@ def list_sandboxes(
|
|
|
158
175
|
|
|
159
176
|
import logging
|
|
160
177
|
logger = logging.getLogger(__name__)
|
|
161
|
-
logger.info("ListSandboxes:
|
|
178
|
+
logger.info(f"ListSandboxes: {request.filter}")
|
|
162
179
|
|
|
163
|
-
# Delegate to the service layer for filtering and pagination
|
|
164
180
|
return sandbox_service.list_sandboxes(request)
|
|
165
181
|
|
|
166
182
|
|
|
@@ -196,7 +212,6 @@ def get_sandbox(
|
|
|
196
212
|
Raises:
|
|
197
213
|
HTTPException: If sandbox not found or access denied
|
|
198
214
|
"""
|
|
199
|
-
# Delegate to the service layer for sandbox lookup
|
|
200
215
|
return sandbox_service.get_sandbox(sandbox_id)
|
|
201
216
|
|
|
202
217
|
|
|
@@ -258,7 +273,6 @@ def delete_sandbox(
|
|
|
258
273
|
Raises:
|
|
259
274
|
HTTPException: If sandbox not found or deletion fails
|
|
260
275
|
"""
|
|
261
|
-
# Delegate to the service layer for deletion
|
|
262
276
|
sandbox_service.delete_sandbox(sandbox_id)
|
|
263
277
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
264
278
|
|
|
@@ -299,7 +313,6 @@ def pause_sandbox(
|
|
|
299
313
|
Raises:
|
|
300
314
|
HTTPException: If sandbox not found or cannot be paused
|
|
301
315
|
"""
|
|
302
|
-
# Delegate to the service layer for pause orchestration
|
|
303
316
|
sandbox_service.pause_sandbox(sandbox_id)
|
|
304
317
|
return Response(status_code=status.HTTP_202_ACCEPTED)
|
|
305
318
|
|
|
@@ -336,7 +349,6 @@ def resume_sandbox(
|
|
|
336
349
|
Raises:
|
|
337
350
|
HTTPException: If sandbox not found or cannot be resumed
|
|
338
351
|
"""
|
|
339
|
-
# Delegate to the service layer for resume orchestration
|
|
340
352
|
sandbox_service.resume_sandbox(sandbox_id)
|
|
341
353
|
return Response(status_code=status.HTTP_202_ACCEPTED)
|
|
342
354
|
|
|
@@ -377,7 +389,6 @@ def renew_sandbox_expiration(
|
|
|
377
389
|
Raises:
|
|
378
390
|
HTTPException: If sandbox not found or renewal fails
|
|
379
391
|
"""
|
|
380
|
-
# Delegate to the service layer for expiration updates
|
|
381
392
|
return sandbox_service.renew_expiration(sandbox_id, request)
|
|
382
393
|
|
|
383
394
|
|
|
@@ -520,6 +531,7 @@ def get_sandbox_endpoint(
|
|
|
520
531
|
use_server_proxy: bool = Query(False, description="Whether to return a server-proxied URL"),
|
|
521
532
|
expires: Optional[int] = Query(None, description="Request a signed route token with this Unix epoch second expiration. Requires ingress gateway with secure_access configured."),
|
|
522
533
|
x_request_id: Optional[str] = Header(None, alias="X-Request-ID", description="Unique request identifier for tracing"),
|
|
534
|
+
response: Response = None, # type: ignore[assignment]
|
|
523
535
|
) -> Endpoint:
|
|
524
536
|
"""
|
|
525
537
|
Get sandbox access endpoint.
|
|
@@ -561,7 +573,6 @@ def get_sandbox_endpoint(
|
|
|
561
573
|
},
|
|
562
574
|
)
|
|
563
575
|
|
|
564
|
-
# Delegate to the service layer for endpoint resolution
|
|
565
576
|
endpoint = sandbox_service.get_endpoint(sandbox_id, port, expires=expires)
|
|
566
577
|
|
|
567
578
|
if use_server_proxy:
|
|
@@ -580,5 +591,19 @@ def get_sandbox_endpoint(
|
|
|
580
591
|
base_url = str(request.base_url).rstrip("/") + mount_prefix
|
|
581
592
|
base_url = base_url.replace("https://", "").replace("http://", "")
|
|
582
593
|
endpoint.endpoint = f"{base_url}/sandboxes/{sandbox_id}/proxy/{port}"
|
|
594
|
+
if endpoint.headers:
|
|
595
|
+
endpoint.headers = {
|
|
596
|
+
key: value
|
|
597
|
+
for key, value in endpoint.headers.items()
|
|
598
|
+
if key.lower() != OPEN_SANDBOX_INGRESS_HEADER.lower()
|
|
599
|
+
} or None
|
|
600
|
+
|
|
601
|
+
# Tell clients the origin of this sandbox. fsb sandboxes (id prefix,
|
|
602
|
+
# mirroring CompositeSandboxService._backend routing) run on golden-image
|
|
603
|
+
# templates and have no sandbox-side egress sidecar; the value space may
|
|
604
|
+
# grow (image/snapshot) as server-side origin tracking matures. Clients
|
|
605
|
+
# treat a missing/unknown value as "not template".
|
|
606
|
+
if response is not None and sandbox_id.startswith("fsb-"):
|
|
607
|
+
response.headers[OPEN_SANDBOX_ORIGIN_HEADER] = SANDBOX_ORIGIN_TEMPLATE
|
|
583
608
|
|
|
584
609
|
return endpoint
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2026
|
|
1
|
+
# Copyright 2026 The OpenSandbox Authors
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -71,13 +71,8 @@ def report_metrics_event(
|
|
|
71
71
|
success=event.success,
|
|
72
72
|
)
|
|
73
73
|
logger.debug(
|
|
74
|
-
"Accepted sandbox.create metrics event sandbox_id
|
|
75
|
-
"duration_ms
|
|
76
|
-
event.
|
|
77
|
-
event.image,
|
|
78
|
-
event.create_duration_ms,
|
|
79
|
-
sdk_language,
|
|
80
|
-
sdk_version,
|
|
81
|
-
event.success,
|
|
74
|
+
f"Accepted sandbox.create metrics event sandbox_id={event.sandbox_id} "
|
|
75
|
+
f"image={event.image} duration_ms={event.create_duration_ms} "
|
|
76
|
+
f"sdk={sdk_language}/{sdk_version} success={event.success}"
|
|
82
77
|
)
|
|
83
78
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Copyright 2026 The OpenSandbox Authors
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
|
|
6
|
+
"""Read/replace/patch policy intent without exposing the Fastlet's loopback handler."""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
10
|
+
from fastapi import APIRouter, HTTPException, Request
|
|
11
|
+
|
|
12
|
+
from opensandbox_server.api import lifecycle
|
|
13
|
+
from opensandbox_server.api.proxy import _proxy_http_request
|
|
14
|
+
from opensandbox_server.api.schema import NetworkPolicy, NetworkRule
|
|
15
|
+
from opensandbox_server.services.composite_service import CompositeSandboxService
|
|
16
|
+
from opensandbox_server.services.fast_sandbox import FastSandboxService
|
|
17
|
+
|
|
18
|
+
router = APIRouter(tags=["Sandboxes"])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _fsb_service():
|
|
22
|
+
service = lifecycle.sandbox_service
|
|
23
|
+
if not isinstance(service, (FastSandboxService, CompositeSandboxService)):
|
|
24
|
+
raise HTTPException(404, detail="Fsb sandbox not found.")
|
|
25
|
+
return service
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@router.get("/sandboxes/{sandbox_id}/networkpolicy")
|
|
29
|
+
async def get_network_policy(request: Request, sandbox_id: str):
|
|
30
|
+
if sandbox_id.startswith("fsb-"):
|
|
31
|
+
return await asyncio.to_thread(_fsb_service().get_network_policy, sandbox_id)
|
|
32
|
+
return await _proxy_http_request(request, sandbox_id, 18080, "policy", internal=True)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.put("/sandboxes/{sandbox_id}/networkpolicy")
|
|
36
|
+
async def replace_network_policy(request: Request, sandbox_id: str, policy: NetworkPolicy):
|
|
37
|
+
if sandbox_id.startswith("fsb-"):
|
|
38
|
+
return await asyncio.to_thread(_fsb_service().replace_network_policy, sandbox_id, policy)
|
|
39
|
+
return await _proxy_http_request(request, sandbox_id, 18080, "policy", internal=True)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@router.patch("/sandboxes/{sandbox_id}/networkpolicy")
|
|
43
|
+
async def patch_network_policy(request: Request, sandbox_id: str, rules: list[NetworkRule]):
|
|
44
|
+
"""Merge rules into the persisted policy (sidecar PATCH semantics).
|
|
45
|
+
|
|
46
|
+
Incoming rules replace existing rules with the same target in place;
|
|
47
|
+
the first rule per target in the payload wins; the current
|
|
48
|
+
defaultAction is preserved.
|
|
49
|
+
"""
|
|
50
|
+
if sandbox_id.startswith("fsb-"):
|
|
51
|
+
return await asyncio.to_thread(_fsb_service().patch_network_policy, sandbox_id, rules)
|
|
52
|
+
return await _proxy_http_request(request, sandbox_id, 18080, "policy", internal=True)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@router.delete("/sandboxes/{sandbox_id}/networkpolicy")
|
|
56
|
+
async def delete_network_policy(request: Request, sandbox_id: str, targets: list[str]):
|
|
57
|
+
"""Remove rules by target (idempotent); the current defaultAction is preserved."""
|
|
58
|
+
if sandbox_id.startswith("fsb-"):
|
|
59
|
+
return await asyncio.to_thread(_fsb_service().delete_network_policy_rules, sandbox_id, targets)
|
|
60
|
+
return await _proxy_http_request(request, sandbox_id, 18080, "policy", internal=True)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2025
|
|
1
|
+
# Copyright 2025 The OpenSandbox Authors
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -70,10 +70,6 @@ def _get_pool_service():
|
|
|
70
70
|
return PoolService(k8s_client, namespace=config.kubernetes.namespace)
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
# ============================================================================
|
|
74
|
-
# Pool CRUD Endpoints
|
|
75
|
-
# ============================================================================
|
|
76
|
-
|
|
77
73
|
@router.post(
|
|
78
74
|
"/pools",
|
|
79
75
|
response_model=PoolResponse,
|