#!/usr/bin/env bash # 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. # Shared helpers for the Kubernetes Python E2E entrypoints: # scripts/python-k8s-e2e.sh, scripts/python-k8s-e2e-ingress.sh # (this file is scripts/common/kubernetes-e2e.sh — library only, not the top-level runner). # Source after setting REPO_ROOT and the usual E2E_* / image env vars. # # Optional: # E2E_SERVER_GATEWAY_ENABLED=true — include server.gateway.* in Helm values (ingress-gateway path). # E2E_GATEWAY_ROUTE_MODE — when gateway enabled: header | uri (default header). Matches chart server.gateway.gatewayRouteMode. k8s_e2e_export_kubeconfig() { export KUBECONFIG="${KUBECONFIG_PATH}" if [ -n "${GITHUB_ENV:-}" ]; then echo "KUBECONFIG=${KUBECONFIG_PATH}" >> "${GITHUB_ENV}" fi } k8s_e2e_setup_kind_and_controller() { cd "${REPO_ROOT}/kubernetes" make setup-test-e2e KIND_CLUSTER="${KIND_CLUSTER}" KIND_K8S_VERSION="${KIND_K8S_VERSION}" kind export kubeconfig --name "${KIND_CLUSTER}" --kubeconfig "${KUBECONFIG_PATH}" make docker-build-controller CONTROLLER_IMG="${CONTROLLER_IMG}" kind load docker-image --name "${KIND_CLUSTER}" "${CONTROLLER_IMG}" make install make deploy CONTROLLER_IMG="${CONTROLLER_IMG}" kubectl wait --for=condition=available --timeout=180s deployment/opensandbox-controller-manager -n opensandbox-system cd "${REPO_ROOT}" } k8s_e2e_build_runtime_images() { docker build -f server/Dockerfile -t "${SERVER_IMG}" server docker build -f components/execd/Dockerfile -t "${EXECD_IMG}" "${REPO_ROOT}" docker build -f components/egress/Dockerfile -t "${EGRESS_IMG}" "${REPO_ROOT}" if [ "${E2E_SERVER_GATEWAY_ENABLED:-false}" = "true" ]; then docker build -f components/ingress/Dockerfile -t "${INGRESS_IMG}" "${REPO_ROOT}" fi docker pull "${SANDBOX_TEST_IMAGE}" } k8s_e2e_kind_load_runtime_images() { kind load docker-image --name "${KIND_CLUSTER}" "${SERVER_IMG}" kind load docker-image --name "${KIND_CLUSTER}" "${EXECD_IMG}" kind load docker-image --name "${KIND_CLUSTER}" "${EGRESS_IMG}" if [ "${E2E_SERVER_GATEWAY_ENABLED:-false}" = "true" ]; then kind load docker-image --name "${KIND_CLUSTER}" "${INGRESS_IMG}" fi kind load docker-image --name "${KIND_CLUSTER}" "${SANDBOX_TEST_IMAGE}" } k8s_e2e_apply_pvc_and_seed() { kubectl get namespace "${E2E_NAMESPACE}" >/dev/null 2>&1 || kubectl create namespace "${E2E_NAMESPACE}" cat < /data/marker.txt echo 'pvc-subpath-marker' > /data/datasets/train/marker.txt volumeMounts: - name: pvc mountPath: /data volumes: - name: pvc persistentVolumeClaim: claimName: ${PVC_NAME} EOF kubectl wait --for=jsonpath='{.status.phase}'=Succeeded --timeout=120s pod/opensandbox-e2e-pvc-seed -n "${E2E_NAMESPACE}" kubectl delete pod/opensandbox-e2e-pvc-seed -n "${E2E_NAMESPACE}" --ignore-not-found=true } k8s_e2e_write_server_helm_values() { local signing_key signing_key=$(openssl rand -base64 32 | tr -d '\n') { cat < "${SERVER_VALUES_FILE}" } k8s_e2e_validate_rendered_config_toml() { python3 - <<'PY' "${REPO_ROOT}" "${SERVER_VALUES_FILE}" import subprocess import sys try: import tomllib except ModuleNotFoundError: import tomli as tomllib repo_root, values_file = sys.argv[1], sys.argv[2] chart_path = f"{repo_root}/kubernetes/charts/opensandbox-server" rendered = subprocess.run( ["helm", "template", "opensandbox-server", chart_path, "-f", values_file], check=True, capture_output=True, text=True, ).stdout config_lines = [] capturing = False for line in rendered.splitlines(): if line == " config.toml: |": capturing = True continue if capturing: if line.startswith("---"): break if line.startswith(" "): config_lines.append(line[4:]) continue if line.strip() == "": config_lines.append("") continue break if not config_lines: raise RuntimeError("Failed to extract config.toml from rendered Helm manifest") tomllib.loads("\n".join(config_lines) + "\n") PY } k8s_e2e_helm_install_server() { kubectl get namespace "${SERVER_NAMESPACE}" >/dev/null 2>&1 || kubectl create namespace "${SERVER_NAMESPACE}" k8s_e2e_validate_rendered_config_toml helm upgrade --install "${SERVER_RELEASE}" "${REPO_ROOT}/kubernetes/charts/opensandbox-server" \ --namespace "${SERVER_NAMESPACE}" \ --create-namespace \ -f "${SERVER_VALUES_FILE}" if ! kubectl wait --for=condition=available --timeout=180s deployment/opensandbox-server -n "${SERVER_NAMESPACE}"; then kubectl get pods -n "${SERVER_NAMESPACE}" -o wide || true kubectl describe deployment/opensandbox-server -n "${SERVER_NAMESPACE}" || true kubectl describe pods -n "${SERVER_NAMESPACE}" -l app.kubernetes.io/name=opensandbox-server || true kubectl logs -n "${SERVER_NAMESPACE}" deployment/opensandbox-server --all-containers=true || true exit 1 fi if [ "${E2E_SERVER_GATEWAY_ENABLED:-false}" = "true" ]; then kubectl wait --for=condition=available --timeout=180s deployment/opensandbox-ingress-gateway -n "${SERVER_NAMESPACE}" fi } k8s_e2e_wait_http_ok() { local url="$1" local i for i in $(seq 1 30); do if curl -fsS "${url}" >/dev/null; then return 0 fi sleep 2 done curl -fsS "${url}" >/dev/null } # Exports for tests/python (see tests/base_e2e_test.get_e2e_sandbox_resource). k8s_e2e_export_sandbox_resource_env() { export OPENSANDBOX_E2E_SANDBOX_CPU="${OPENSANDBOX_E2E_SANDBOX_CPU:-250m}" export OPENSANDBOX_E2E_SANDBOX_MEMORY="${OPENSANDBOX_E2E_SANDBOX_MEMORY:-512Mi}" } k8s_e2e_generate_sdk_and_run_kubernetes_mini() { cd "${REPO_ROOT}/sdks/sandbox/python" make generate-api cd "${REPO_ROOT}/tests/python" uv sync --all-extras --refresh make test-kubernetes-mini }