From 96ae0118b5f7b95db5b53731799f848e34a1e85d Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Thu, 19 Feb 2026 16:29:45 -0800 Subject: [PATCH 01/11] feat: add OpenAPI doc generation script and improve envd spec Add scripts/generate_openapi.py that merges multiple OpenAPI sources (proto-generated Connect RPC specs, envd REST spec, platform API spec) into a single e2b-openapi.yml for Mintlify documentation. Includes post-processing for empty responses, 502 sandbox-not-found errors, streaming endpoint placeholders, and orphaned schema cleanup. Update packages/envd/spec/envd.yaml with accurate response types, operationIds, improved parameter descriptions, example error responses, and a 406 NotAcceptable response for unsupported encodings. --- packages/envd/spec/envd.yaml | 76 +++- scripts/generate_openapi.py | 679 +++++++++++++++++++++++++++++++++++ 2 files changed, 739 insertions(+), 16 deletions(-) create mode 100644 scripts/generate_openapi.py diff --git a/packages/envd/spec/envd.yaml b/packages/envd/spec/envd.yaml index 83091f1ab5..0522c070ff 100644 --- a/packages/envd/spec/envd.yaml +++ b/packages/envd/spec/envd.yaml @@ -10,6 +10,7 @@ tags: paths: /health: get: + operationId: getHealth summary: Check the health of the service responses: "204": @@ -17,9 +18,10 @@ paths: /metrics: get: + operationId: getMetrics summary: Get the stats of the service security: - - AccessTokenAuth: [] + - SandboxAccessTokenAuth: [] - {} responses: "200": @@ -31,9 +33,10 @@ paths: /init: post: + operationId: initSandbox summary: Set initial vars, ensure the time and metadata is synced with the host security: - - AccessTokenAuth: [] + - SandboxAccessTokenAuth: [] - {} requestBody: content: @@ -70,9 +73,10 @@ paths: /envs: get: + operationId: getEnvVars summary: Get the environment variables security: - - AccessTokenAuth: [] + - SandboxAccessTokenAuth: [] - {} responses: "200": @@ -84,10 +88,11 @@ paths: /files: get: + operationId: downloadFile summary: Download a file tags: [files] security: - - AccessTokenAuth: [] + - SandboxAccessTokenAuth: [] - {} parameters: - $ref: "#/components/parameters/FilePath" @@ -97,19 +102,22 @@ paths: responses: "200": $ref: "#/components/responses/DownloadSuccess" - "401": - $ref: "#/components/responses/InvalidUser" "400": $ref: "#/components/responses/InvalidPath" + "401": + $ref: "#/components/responses/InvalidUser" "404": $ref: "#/components/responses/FileNotFound" + "406": + $ref: "#/components/responses/NotAcceptable" "500": $ref: "#/components/responses/InternalServerError" post: + operationId: uploadFile summary: Upload a file and ensure the parent directories exist. If the file exists, it will be overwritten. tags: [files] security: - - AccessTokenAuth: [] + - SandboxAccessTokenAuth: [] - {} parameters: - $ref: "#/components/parameters/FilePath" @@ -132,7 +140,7 @@ paths: components: securitySchemes: - AccessTokenAuth: + SandboxAccessTokenAuth: type: apiKey in: header name: X-Access-Token @@ -141,29 +149,29 @@ components: FilePath: name: path in: query - required: false - description: Path to the file, URL encoded. Can be relative to user's home directory. + required: true + description: Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). schema: type: string User: name: username in: query required: false - description: User used for setting the owner, or resolving relative paths. + description: User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. schema: type: string Signature: name: signature in: query required: false - description: Signature used for file access permission verification. + description: HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". schema: type: string SignatureExpiration: name: signature_expiration in: query required: false - description: Signature expiration used for defining the expiration time of the signature. + description: Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. schema: type: integer @@ -188,45 +196,73 @@ components: type: array items: $ref: "#/components/schemas/EntryInfo" + example: + - path: "/home/user/hello.txt" + name: "hello.txt" + type: "file" DownloadSuccess: - description: Entire file downloaded successfully. + description: File content. Content-Type is detected from the file extension (defaults to application/octet-stream). Content-Disposition header contains the filename. content: application/octet-stream: schema: type: string format: binary - description: The file content + description: The raw file content + NotAcceptable: + description: Requested encoding is not supported + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + message: "no acceptable encoding found, supported: [identity, gzip]" + code: 406 InvalidPath: description: Invalid path content: application/json: schema: $ref: "#/components/schemas/Error" + example: + message: "path '/home/user/docs' is a directory" + code: 400 InternalServerError: description: Internal server error content: application/json: schema: $ref: "#/components/schemas/Error" + example: + message: "error opening file '/home/user/file.txt': permission denied" + code: 500 FileNotFound: description: File not found content: application/json: schema: $ref: "#/components/schemas/Error" + example: + message: "path '/home/user/missing.txt' does not exist" + code: 404 InvalidUser: description: Invalid user content: application/json: schema: $ref: "#/components/schemas/Error" + example: + message: "error looking up user 'nonexistent': user: unknown user nonexistent" + code: 401 NotEnoughDiskSpace: description: Not enough disk space content: application/json: schema: $ref: "#/components/schemas/Error" + example: + message: "not enough disk space available" + code: 507 schemas: Error: @@ -283,6 +319,12 @@ components: mem_used: type: integer description: Used virtual memory in bytes + mem_total_mib: + type: integer + description: Total virtual memory in MiB + mem_used_mib: + type: integer + description: Used virtual memory in MiB disk_used: type: integer description: Used disk space in bytes @@ -291,13 +333,15 @@ components: description: Total disk space in bytes VolumeMount: type: object - description: Volume + description: NFS volume mount configuration additionalProperties: false properties: nfs_target: type: string + description: NFS server target address path: type: string + description: Mount path inside the sandbox required: - nfs_target - path diff --git a/scripts/generate_openapi.py b/scripts/generate_openapi.py new file mode 100644 index 0000000000..81b1e22927 --- /dev/null +++ b/scripts/generate_openapi.py @@ -0,0 +1,679 @@ +#!/usr/bin/env python3 +"""Generate a merged OpenAPI spec for the full E2B developer-facing API. + +Combines multiple sources into a single e2b-openapi.yml: + + Sandbox API (served on -.e2b.app): + - Proto-generated OpenAPI for process/filesystem Connect RPC + - Hand-written REST spec (packages/envd/spec/envd.yaml) + - Auto-generated stubs for streaming RPCs (parsed from .proto files) + + Platform API (served on api.e2b.app): + - Main E2B API spec (spec/openapi.yml) + +Usage: + python3 scripts/generate-openapi/envd.py + +Outputs e2b-openapi.yml in the current working directory. +Requires: Docker, PyYAML (pip install pyyaml). +""" + +from __future__ import annotations + +import os +import re +import subprocess +import sys +import tempfile +from dataclasses import dataclass +from glob import glob +from typing import Any + +import yaml + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +REPO_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) + +# Sandbox (envd) specs +ENVD_SPEC_DIR = os.path.join(REPO_ROOT, "packages/envd/spec") +ENVD_REST_SPEC = os.path.join(ENVD_SPEC_DIR, "envd.yaml") + +# Platform API specs +API_SPEC = os.path.join(REPO_ROOT, "spec/openapi.yml") + +DOCKER_IMAGE = "protoc-gen-connect-openapi" + +DOCKERFILE = """\ +FROM golang:1.25-alpine +RUN apk add --no-cache git +RUN go install github.com/bufbuild/buf/cmd/buf@v1.50.0 +RUN go install github.com/sudorandom/protoc-gen-connect-openapi@latest +ENV PATH="/go/bin:${PATH}" +""" + +BUF_GEN_YAML = """\ +version: v1 +plugins: + - plugin: connect-openapi + out: /output + opt: + - format=yaml +""" + +# Server definitions for the two API surfaces +SANDBOX_SERVER = { + "url": "https://{port}-{sandboxID}.e2b.app", + "description": "Sandbox API (envd) — runs inside each sandbox", + "variables": { + "port": {"default": "49983", "description": "Port number"}, + "sandboxID": {"default": "{sandbox-id}", "description": "Sandbox identifier"}, + }, +} + +PLATFORM_SERVER = { + "url": "https://api.e2b.app", + "description": "E2B Platform API", +} + +# Tag used to mark sandbox-specific paths so we can attach the right server +SANDBOX_TAG = "x-e2b-server" + +# Security scheme name for envd endpoints (must not collide with platform's AccessTokenAuth) +SANDBOX_AUTH_SCHEME = "SandboxAccessTokenAuth" + +# --------------------------------------------------------------------------- +# Proto parsing — auto-detect streaming RPCs +# --------------------------------------------------------------------------- + +@dataclass +class RpcMethod: + """An RPC method parsed from a .proto file.""" + + package: str + service: str + method: str + request_type: str + response_type: str + client_streaming: bool + server_streaming: bool + comment: str + + @property + def path(self) -> str: + return f"/{self.package}.{self.service}/{self.method}" + + @property + def tag(self) -> str: + return f"{self.package}.{self.service}" + + @property + def operation_id(self) -> str: + return f"{self.package}.{self.service}.{self.method}" + + @property + def request_schema_ref(self) -> str: + return f"#/components/schemas/{self.package}.{self.request_type}" + + @property + def response_schema_ref(self) -> str: + return f"#/components/schemas/{self.package}.{self.response_type}" + + @property + def is_streaming(self) -> bool: + return self.client_streaming or self.server_streaming + + @property + def streaming_label(self) -> str: + if self.client_streaming and self.server_streaming: + return "Bidirectional-streaming" + if self.client_streaming: + return "Client-streaming" + if self.server_streaming: + return "Server-streaming" + return "Unary" + + +_PACKAGE_RE = re.compile(r"^package\s+(\w+)\s*;", re.MULTILINE) +_SERVICE_RE = re.compile(r"service\s+(\w+)\s*\{", re.MULTILINE) +_RPC_RE = re.compile( + r"rpc\s+(\w+)\s*\(\s*(stream\s+)?(\w+)\s*\)\s*returns\s*\(\s*(stream\s+)?(\w+)\s*\)" +) + + +def parse_proto_file(path: str) -> list[RpcMethod]: + """Parse a .proto file and return all RPC methods found.""" + with open(path) as f: + content = f.read() + + pkg_match = _PACKAGE_RE.search(content) + if not pkg_match: + return [] + package = pkg_match.group(1) + + methods: list[RpcMethod] = [] + + for svc_match in _SERVICE_RE.finditer(content): + service_name = svc_match.group(1) + brace_start = content.index("{", svc_match.start()) + depth, pos = 1, brace_start + 1 + while depth > 0 and pos < len(content): + if content[pos] == "{": + depth += 1 + elif content[pos] == "}": + depth -= 1 + pos += 1 + service_body = content[brace_start:pos] + + for rpc_match in _RPC_RE.finditer(service_body): + rpc_start = service_body.rfind("\n", 0, rpc_match.start()) + comment = _extract_comment(service_body, rpc_start) + + methods.append(RpcMethod( + package=package, + service=service_name, + method=rpc_match.group(1), + request_type=rpc_match.group(3), + response_type=rpc_match.group(5), + client_streaming=bool(rpc_match.group(2)), + server_streaming=bool(rpc_match.group(4)), + comment=comment, + )) + + return methods + + +def _extract_comment(text: str, before_pos: int) -> str: + """Extract // comment lines immediately above a position in text.""" + lines = text[:before_pos].rstrip().split("\n") + comment_lines: list[str] = [] + for line in reversed(lines): + stripped = line.strip() + if stripped.startswith("//"): + comment_lines.append(stripped.lstrip("/ ")) + elif stripped == "": + continue + else: + break + comment_lines.reverse() + return " ".join(comment_lines) + + +def find_streaming_rpcs(spec_dir: str) -> list[RpcMethod]: + """Scan all .proto files under spec_dir and return streaming RPCs.""" + streaming: list[RpcMethod] = [] + for proto_path in sorted(glob(os.path.join(spec_dir, "**/*.proto"), recursive=True)): + for rpc in parse_proto_file(proto_path): + if rpc.is_streaming: + streaming.append(rpc) + return streaming + + +def build_streaming_path(rpc: RpcMethod) -> dict[str, Any]: + """Build an OpenAPI path item for a streaming RPC.""" + description = ( + f"{rpc.streaming_label} RPC. " + f"{rpc.comment + '. ' if rpc.comment else ''}" + f"Use the Connect protocol with streaming support." + ) + return { + "post": { + "tags": [rpc.tag], + "summary": rpc.method, + "description": description, + "operationId": rpc.operation_id, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": rpc.request_schema_ref} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": f"Stream of {rpc.response_type} events", + "content": { + "application/json": { + "schema": {"$ref": rpc.response_schema_ref} + } + }, + }, + }, + } + } + + +# --------------------------------------------------------------------------- +# Docker build & proto generation +# --------------------------------------------------------------------------- + +def docker_build_image() -> None: + """Build the Docker image with buf + protoc-gen-connect-openapi.""" + print("==> Building Docker image") + with tempfile.NamedTemporaryFile(mode="w", suffix=".Dockerfile", delete=False) as f: + f.write(DOCKERFILE) + f.flush() + subprocess.run( + ["docker", "build", "-t", DOCKER_IMAGE, "-f", f.name, "."], + check=True, + cwd=REPO_ROOT, + ) + os.unlink(f.name) + + +def docker_generate_specs() -> list[str]: + """Run buf generate inside Docker, return list of generated YAML strings.""" + print("==> Generating OpenAPI specs from proto files") + with tempfile.TemporaryDirectory() as tmpdir: + buf_gen_path = os.path.join(tmpdir, "buf.gen.yaml") + with open(buf_gen_path, "w") as f: + f.write(BUF_GEN_YAML) + + output_dir = os.path.join(tmpdir, "output") + os.makedirs(output_dir) + + subprocess.run( + [ + "docker", "run", "--rm", + "-v", f"{ENVD_SPEC_DIR}:/spec:ro", + "-v", f"{buf_gen_path}:/config/buf.gen.yaml:ro", + "-v", f"{output_dir}:/output", + DOCKER_IMAGE, + "sh", "-c", + "cd /spec && buf generate --template /config/buf.gen.yaml", + ], + check=True, + ) + + generated: list[str] = [] + for root, _, files in os.walk(output_dir): + for name in sorted(files): + if name.endswith((".yaml", ".yml")): + path = os.path.join(root, name) + rel = os.path.relpath(path, output_dir) + print(f" Generated: {rel}") + with open(path) as f: + generated.append(f.read()) + + if not generated: + print("ERROR: No files were generated", file=sys.stderr) + sys.exit(1) + + return generated + + +# --------------------------------------------------------------------------- +# OpenAPI merging & post-processing +# --------------------------------------------------------------------------- + +def load_yaml_file(path: str) -> str: + """Load a YAML file and return its raw content.""" + print(f"==> Loading spec: {os.path.relpath(path, REPO_ROOT)}") + with open(path) as f: + return f.read() + + +def merge_specs(raw_docs: list[str]) -> dict[str, Any]: + """Merge multiple raw YAML OpenAPI docs into a single spec.""" + merged: dict[str, Any] = { + "openapi": "3.1.0", + "info": { + "title": "E2B API", + "version": "0.1.0", + "description": ( + "Complete E2B developer API. " + "Platform endpoints are served on api.e2b.app. " + "Sandbox endpoints (envd) are served on {port}-{sandboxID}.e2b.app." + ), + }, + "servers": [PLATFORM_SERVER], + "paths": {}, + "components": {}, + } + + for raw in raw_docs: + doc = yaml.safe_load(raw) + if not doc: + continue + + for path, methods in doc.get("paths", {}).items(): + merged["paths"][path] = methods + + for section, entries in doc.get("components", {}).items(): + if isinstance(entries, dict): + merged["components"].setdefault(section, {}).update(entries) + + if "tags" in doc: + merged.setdefault("tags", []).extend(doc["tags"]) + + if "security" in doc: + merged.setdefault("security", []).extend(doc["security"]) + + return merged + + +def tag_paths_with_server( + spec: dict[str, Any], + paths: set[str], + server: dict[str, Any], +) -> None: + """Attach a specific server override to a set of paths. + + OpenAPI 3.1 allows per-path server overrides so clients know which + base URL to use for each endpoint. + """ + for path, path_item in spec["paths"].items(): + if path in paths: + path_item["servers"] = [server] + + +def fill_streaming_endpoints(spec: dict[str, Any], streaming_rpcs: list[RpcMethod]) -> None: + """Replace empty {} streaming path items with proper OpenAPI definitions. + + protoc-gen-connect-openapi emits {} for streaming RPCs because OpenAPI + has no native streaming representation. We detect these from the proto + files and fill them in with proper request/response schemas. + """ + for rpc in streaming_rpcs: + if rpc.path in spec["paths"]: + print(f" Filling streaming endpoint: {rpc.path} ({rpc.streaming_label})") + spec["paths"][rpc.path] = build_streaming_path(rpc) + + +def apply_sandbox_auth(spec: dict[str, Any], envd_paths: set[str]) -> None: + """Ensure all envd/sandbox endpoints declare the SandboxAccessTokenAuth security. + + The hand-written envd.yaml already has security declarations, but the + proto-generated Connect RPC endpoints don't. Add optional auth + (SandboxAccessTokenAuth or anonymous) to any envd endpoint missing it. + """ + auth_security = [{SANDBOX_AUTH_SCHEME: []}, {}] + for path in envd_paths: + path_item = spec["paths"].get(path) + if not path_item: + continue + for method in ("get", "post", "put", "patch", "delete"): + op = path_item.get(method) + if op and "security" not in op: + op["security"] = auth_security + + +def fix_security_schemes(spec: dict[str, Any]) -> None: + """Fix invalid apiKey securityScheme syntax. + + The source envd.yaml uses `scheme: header` which is wrong for + type: apiKey — OpenAPI requires `in: header` instead. + """ + for scheme in spec.get("components", {}).get("securitySchemes", {}).values(): + if scheme.get("type") == "apiKey" and "scheme" in scheme: + scheme["in"] = scheme.pop("scheme") + + +def _strip_supabase_security(path_item: dict[str, Any]) -> None: + """Remove Supabase security entries from all operations in a path item. + + Each operation's security list is an OR of auth options. We remove + any option that references a Supabase scheme, keeping the rest. + """ + for method in ("get", "post", "put", "patch", "delete", "head", "options"): + op = path_item.get(method) + if not op or "security" not in op: + continue + op["security"] = [ + sec_req for sec_req in op["security"] + if not any("supabase" in key.lower() for key in sec_req) + ] + + +def _has_admin_token_security(path_item: dict[str, Any]) -> bool: + """Check if any operation in a path item references AdminToken auth.""" + for method in ("get", "post", "put", "patch", "delete", "head", "options"): + op = path_item.get(method) + if not op: + continue + for sec_req in op.get("security", []): + if any("admin" in key.lower() for key in sec_req): + return True + return False + + +def filter_paths(spec: dict[str, Any]) -> None: + """Clean up paths that should not appear in the public spec. + + - Removes volume, access-token, and api-key endpoints + - Removes endpoints using AdminToken auth + - Strips Supabase auth entries from all operations + - Removes Supabase and AdminToken securityScheme definitions + """ + # Remove excluded paths + excluded_prefixes = ("/volumes", "/access-tokens", "/api-keys") + excluded_exact = {"/v2/sandboxes/{sandboxID}/logs", "/init"} + to_remove = [ + p for p in spec["paths"] + if p.startswith(excluded_prefixes) or p in excluded_exact + ] + + # Remove admin-only paths + for path, path_item in spec["paths"].items(): + if path not in to_remove and _has_admin_token_security(path_item): + to_remove.append(path) + + for path in to_remove: + del spec["paths"][path] + if to_remove: + print(f"==> Removed {len(to_remove)} paths (volumes + admin)") + + # Strip supabase security entries from all operations + for path_item in spec["paths"].values(): + _strip_supabase_security(path_item) + + # Remove supabase and admin security scheme definitions + schemes = spec.get("components", {}).get("securitySchemes", {}) + remove_keys = [k for k in schemes if "supabase" in k.lower() or "admin" in k.lower()] + for key in remove_keys: + del schemes[key] + if remove_keys: + print(f"==> Removed {len(remove_keys)} internal security schemes") + + +def remove_orphaned_schemas(spec: dict[str, Any]) -> None: + """Remove component schemas that are not referenced anywhere in the spec. + Runs iteratively since removing schemas may orphan others.""" + all_orphaned: list[str] = [] + + while True: + spec_text = "" + # Serialize paths + top-level refs (excluding components.schemas itself) + for section in ("paths", "security"): + if section in spec: + spec_text += yaml.dump(spec[section], default_flow_style=False) + for section, entries in spec.get("components", {}).items(): + if section != "schemas": + spec_text += yaml.dump(entries, default_flow_style=False) + # Also check cross-references within schemas + schemas = spec.get("components", {}).get("schemas", {}) + schema_text = yaml.dump(schemas, default_flow_style=False) + + orphaned = [] + for name in list(schemas.keys()): + ref_pattern = f"schemas/{name}" + # Referenced from paths/responses/params or from other schemas + if ref_pattern not in spec_text and ref_pattern not in schema_text.replace( + f"schemas/{name}:", "" # exclude self-definition line + ): + # Double-check: not referenced by any other schema + used = False + for other_name, other_schema in schemas.items(): + if other_name == name: + continue + if ref_pattern in yaml.dump(other_schema, default_flow_style=False): + used = True + break + if not used: + orphaned.append(name) + + if not orphaned: + break + + for name in orphaned: + del schemas[name] + all_orphaned.extend(orphaned) + + if all_orphaned: + print(f"==> Removed {len(all_orphaned)} orphaned schemas: {', '.join(sorted(all_orphaned))}") + + +SANDBOX_NOT_FOUND_RESPONSE = { + "description": "Sandbox not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["sandboxId", "message", "code"], + "properties": { + "sandboxId": { + "type": "string", + "description": "Identifier of the sandbox", + "example": "i1234abcd5678efgh90jk", + }, + "message": { + "type": "string", + "description": "Error message", + "example": "The sandbox was not found", + }, + "code": { + "type": "integer", + "description": "Error code", + "example": 502, + }, + }, + } + } + }, +} + + +EMPTY_RESPONSE_CONTENT = { + "application/json": { + "schema": {"type": "object", "description": "Empty response"} + } +} + + +def add_sandbox_not_found(spec: dict[str, Any], envd_paths: set[str]) -> None: + """Add a 502 response to all sandbox/envd endpoints. + + The load balancer returns 502 when a sandbox is not found. + """ + count = 0 + for path in envd_paths: + path_item = spec["paths"].get(path) + if not path_item: + continue + for method in ("get", "post", "put", "patch", "delete"): + op = path_item.get(method) + if op and "502" not in op.get("responses", {}): + op.setdefault("responses", {})["502"] = SANDBOX_NOT_FOUND_RESPONSE + count += 1 + if count: + print(f"==> Added 502 sandbox-not-found response to {count} operations") + + +def fill_empty_responses(spec: dict[str, Any]) -> None: + """Add an empty content block to any 2xx response that lacks one. + + Mintlify requires a content block on every response to render correctly. + """ + filled = 0 + stripped = 0 + for path, path_item in spec.get("paths", {}).items(): + for method in ("get", "post", "put", "patch", "delete", "head", "options"): + op = path_item.get(method) + if not op: + continue + responses = op.get("responses", {}) + # Remove "default" responses (generic Connect error envelopes) + if "default" in responses: + del responses["default"] + stripped += 1 + for status, resp in responses.items(): + if isinstance(resp, dict) and str(status).startswith("2") and "content" not in resp: + resp["content"] = EMPTY_RESPONSE_CONTENT + filled += 1 + if filled: + print(f"==> Added empty content block to {filled} responses") + if stripped: + print(f"==> Removed {stripped} default error responses") + + +# --------------------------------------------------------------------------- +# Entrypoint +# --------------------------------------------------------------------------- + +def main() -> None: + docker_build_image() + + # --- Sandbox API (envd) --- + proto_docs = docker_generate_specs() + envd_rest_doc = load_yaml_file(ENVD_REST_SPEC) + + # Track which paths come from envd so we can set their server + envd_raw_docs = [envd_rest_doc] + proto_docs + envd_paths: set[str] = set() + for raw in envd_raw_docs: + doc = yaml.safe_load(raw) + if doc and "paths" in doc: + envd_paths.update(doc["paths"].keys()) + + # --- Platform API --- + api_doc = load_yaml_file(API_SPEC) + + # --- Merge everything --- + # Order: envd first, then platform API (platform schemas take precedence + # for shared names like Error since they're more complete) + merged = merge_specs(envd_raw_docs + [api_doc]) + + # Auto-detect and fill streaming RPC endpoints + streaming_rpcs = find_streaming_rpcs(ENVD_SPEC_DIR) + print(f"==> Found {len(streaming_rpcs)} streaming RPCs in proto files") + fill_streaming_endpoints(merged, streaming_rpcs) + for rpc in streaming_rpcs: + envd_paths.add(rpc.path) + + # Attach per-path server overrides so each path has exactly one server + tag_paths_with_server(merged, envd_paths, SANDBOX_SERVER) + platform_paths = set(merged["paths"].keys()) - envd_paths + tag_paths_with_server(merged, platform_paths, PLATFORM_SERVER) + + # Ensure all sandbox endpoints declare auth + apply_sandbox_auth(merged, envd_paths) + + # Add 502 sandbox-not-found to all envd endpoints + add_sandbox_not_found(merged, envd_paths) + + # Fix known issues + fix_security_schemes(merged) + + # Remove internal/unwanted paths + filter_paths(merged) + + # Ensure all 2xx responses have a content block (required by Mintlify) + fill_empty_responses(merged) + + # Clean up unreferenced schemas left over from filtered paths + remove_orphaned_schemas(merged) + + # Write output + output_path = os.path.join(os.getcwd(), "e2b-openapi.yml") + with open(output_path, "w") as f: + yaml.dump(merged, f, default_flow_style=False, sort_keys=False, allow_unicode=True) + + print(f"==> Written to {output_path}") + + +if __name__ == "__main__": + main() From 47fe918fe3e0ac655e5cff18d705d0f9bb466713 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 20 Feb 2026 00:31:07 +0000 Subject: [PATCH 02/11] chore: auto-commit generated changes --- packages/envd/internal/api/api.gen.go | 146 ++++++---- .../internal/sandbox/envd/envd.gen.go | 62 ++-- tests/integration/internal/envd/generated.go | 274 +++++++++--------- 3 files changed, 266 insertions(+), 216 deletions(-) diff --git a/packages/envd/internal/api/api.gen.go b/packages/envd/internal/api/api.gen.go index 512747b05f..2c33384a4c 100644 --- a/packages/envd/internal/api/api.gen.go +++ b/packages/envd/internal/api/api.gen.go @@ -15,7 +15,7 @@ import ( ) const ( - AccessTokenAuthScopes = "AccessTokenAuth.Scopes" + SandboxAccessTokenAuthScopes = "SandboxAccessTokenAuth.Scopes" ) // Defines values for EntryInfoType. @@ -67,17 +67,26 @@ type Metrics struct { // MemTotal Total virtual memory in bytes MemTotal *int `json:"mem_total,omitempty"` + // MemTotalMib Total virtual memory in MiB + MemTotalMib *int `json:"mem_total_mib,omitempty"` + // MemUsed Used virtual memory in bytes MemUsed *int `json:"mem_used,omitempty"` + // MemUsedMib Used virtual memory in MiB + MemUsedMib *int `json:"mem_used_mib,omitempty"` + // Ts Unix timestamp in UTC for current sandbox time Ts *int64 `json:"ts,omitempty"` } -// VolumeMount Volume +// VolumeMount NFS volume mount configuration type VolumeMount struct { + // NfsTarget NFS server target address NfsTarget string `json:"nfs_target"` - Path string `json:"path"` + + // Path Mount path inside the sandbox + Path string `json:"path"` } // FilePath defines model for FilePath. @@ -104,49 +113,52 @@ type InvalidPath = Error // InvalidUser defines model for InvalidUser. type InvalidUser = Error +// NotAcceptable defines model for NotAcceptable. +type NotAcceptable = Error + // NotEnoughDiskSpace defines model for NotEnoughDiskSpace. type NotEnoughDiskSpace = Error // UploadSuccess defines model for UploadSuccess. type UploadSuccess = []EntryInfo -// GetFilesParams defines parameters for GetFiles. -type GetFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. - Path *FilePath `form:"path,omitempty" json:"path,omitempty"` +// DownloadFileParams defines parameters for DownloadFile. +type DownloadFileParams struct { + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). + Path FilePath `form:"path" json:"path"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// PostFilesMultipartBody defines parameters for PostFiles. -type PostFilesMultipartBody struct { +// UploadFileMultipartBody defines parameters for UploadFile. +type UploadFileMultipartBody struct { File *openapi_types.File `json:"file,omitempty"` } -// PostFilesParams defines parameters for PostFiles. -type PostFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. - Path *FilePath `form:"path,omitempty" json:"path,omitempty"` +// UploadFileParams defines parameters for UploadFile. +type UploadFileParams struct { + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). + Path FilePath `form:"path" json:"path"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// PostInitJSONBody defines parameters for PostInit. -type PostInitJSONBody struct { +// InitSandboxJSONBody defines parameters for InitSandbox. +type InitSandboxJSONBody struct { // AccessToken Access token for secure access to envd service AccessToken *SecureToken `json:"accessToken,omitempty"` @@ -167,29 +179,29 @@ type PostInitJSONBody struct { VolumeMounts *[]VolumeMount `json:"volumeMounts,omitempty"` } -// PostFilesMultipartRequestBody defines body for PostFiles for multipart/form-data ContentType. -type PostFilesMultipartRequestBody PostFilesMultipartBody +// UploadFileMultipartRequestBody defines body for UploadFile for multipart/form-data ContentType. +type UploadFileMultipartRequestBody UploadFileMultipartBody -// PostInitJSONRequestBody defines body for PostInit for application/json ContentType. -type PostInitJSONRequestBody PostInitJSONBody +// InitSandboxJSONRequestBody defines body for InitSandbox for application/json ContentType. +type InitSandboxJSONRequestBody InitSandboxJSONBody // ServerInterface represents all server handlers. type ServerInterface interface { // Get the environment variables // (GET /envs) - GetEnvs(w http.ResponseWriter, r *http.Request) + GetEnvVars(w http.ResponseWriter, r *http.Request) // Download a file // (GET /files) - GetFiles(w http.ResponseWriter, r *http.Request, params GetFilesParams) + DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) // Upload a file and ensure the parent directories exist. If the file exists, it will be overwritten. // (POST /files) - PostFiles(w http.ResponseWriter, r *http.Request, params PostFilesParams) + UploadFile(w http.ResponseWriter, r *http.Request, params UploadFileParams) // Check the health of the service // (GET /health) GetHealth(w http.ResponseWriter, r *http.Request) // Set initial vars, ensure the time and metadata is synced with the host // (POST /init) - PostInit(w http.ResponseWriter, r *http.Request) + InitSandbox(w http.ResponseWriter, r *http.Request) // Get the stats of the service // (GET /metrics) GetMetrics(w http.ResponseWriter, r *http.Request) @@ -201,19 +213,19 @@ type Unimplemented struct{} // Get the environment variables // (GET /envs) -func (_ Unimplemented) GetEnvs(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) GetEnvVars(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } // Download a file // (GET /files) -func (_ Unimplemented) GetFiles(w http.ResponseWriter, r *http.Request, params GetFilesParams) { +func (_ Unimplemented) DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) { w.WriteHeader(http.StatusNotImplemented) } // Upload a file and ensure the parent directories exist. If the file exists, it will be overwritten. // (POST /files) -func (_ Unimplemented) PostFiles(w http.ResponseWriter, r *http.Request, params PostFilesParams) { +func (_ Unimplemented) UploadFile(w http.ResponseWriter, r *http.Request, params UploadFileParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -225,7 +237,7 @@ func (_ Unimplemented) GetHealth(w http.ResponseWriter, r *http.Request) { // Set initial vars, ensure the time and metadata is synced with the host // (POST /init) -func (_ Unimplemented) PostInit(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) InitSandbox(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -244,17 +256,17 @@ type ServerInterfaceWrapper struct { type MiddlewareFunc func(http.Handler) http.Handler -// GetEnvs operation middleware -func (siw *ServerInterfaceWrapper) GetEnvs(w http.ResponseWriter, r *http.Request) { +// GetEnvVars operation middleware +func (siw *ServerInterfaceWrapper) GetEnvVars(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ctx = context.WithValue(ctx, AccessTokenAuthScopes, []string{}) + ctx = context.WithValue(ctx, SandboxAccessTokenAuthScopes, []string{}) r = r.WithContext(ctx) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetEnvs(w, r) + siw.Handler.GetEnvVars(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -264,23 +276,30 @@ func (siw *ServerInterfaceWrapper) GetEnvs(w http.ResponseWriter, r *http.Reques handler.ServeHTTP(w, r) } -// GetFiles operation middleware -func (siw *ServerInterfaceWrapper) GetFiles(w http.ResponseWriter, r *http.Request) { +// DownloadFile operation middleware +func (siw *ServerInterfaceWrapper) DownloadFile(w http.ResponseWriter, r *http.Request) { var err error ctx := r.Context() - ctx = context.WithValue(ctx, AccessTokenAuthScopes, []string{}) + ctx = context.WithValue(ctx, SandboxAccessTokenAuthScopes, []string{}) r = r.WithContext(ctx) // Parameter object where we will unmarshal all parameters from the context - var params GetFilesParams + var params DownloadFileParams + + // ------------- Required query parameter "path" ------------- - // ------------- Optional query parameter "path" ------------- + if paramValue := r.URL.Query().Get("path"); paramValue != "" { - err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) return @@ -311,7 +330,7 @@ func (siw *ServerInterfaceWrapper) GetFiles(w http.ResponseWriter, r *http.Reque } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetFiles(w, r, params) + siw.Handler.DownloadFile(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -321,23 +340,30 @@ func (siw *ServerInterfaceWrapper) GetFiles(w http.ResponseWriter, r *http.Reque handler.ServeHTTP(w, r) } -// PostFiles operation middleware -func (siw *ServerInterfaceWrapper) PostFiles(w http.ResponseWriter, r *http.Request) { +// UploadFile operation middleware +func (siw *ServerInterfaceWrapper) UploadFile(w http.ResponseWriter, r *http.Request) { var err error ctx := r.Context() - ctx = context.WithValue(ctx, AccessTokenAuthScopes, []string{}) + ctx = context.WithValue(ctx, SandboxAccessTokenAuthScopes, []string{}) r = r.WithContext(ctx) // Parameter object where we will unmarshal all parameters from the context - var params PostFilesParams + var params UploadFileParams + + // ------------- Required query parameter "path" ------------- - // ------------- Optional query parameter "path" ------------- + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } - err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) return @@ -368,7 +394,7 @@ func (siw *ServerInterfaceWrapper) PostFiles(w http.ResponseWriter, r *http.Requ } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.PostFiles(w, r, params) + siw.Handler.UploadFile(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -392,17 +418,17 @@ func (siw *ServerInterfaceWrapper) GetHealth(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r) } -// PostInit operation middleware -func (siw *ServerInterfaceWrapper) PostInit(w http.ResponseWriter, r *http.Request) { +// InitSandbox operation middleware +func (siw *ServerInterfaceWrapper) InitSandbox(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ctx = context.WithValue(ctx, AccessTokenAuthScopes, []string{}) + ctx = context.WithValue(ctx, SandboxAccessTokenAuthScopes, []string{}) r = r.WithContext(ctx) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.PostInit(w, r) + siw.Handler.InitSandbox(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -417,7 +443,7 @@ func (siw *ServerInterfaceWrapper) GetMetrics(w http.ResponseWriter, r *http.Req ctx := r.Context() - ctx = context.WithValue(ctx, AccessTokenAuthScopes, []string{}) + ctx = context.WithValue(ctx, SandboxAccessTokenAuthScopes, []string{}) r = r.WithContext(ctx) @@ -546,19 +572,19 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl } r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/envs", wrapper.GetEnvs) + r.Get(options.BaseURL+"/envs", wrapper.GetEnvVars) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/files", wrapper.GetFiles) + r.Get(options.BaseURL+"/files", wrapper.DownloadFile) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/files", wrapper.PostFiles) + r.Post(options.BaseURL+"/files", wrapper.UploadFile) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/health", wrapper.GetHealth) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/init", wrapper.PostInit) + r.Post(options.BaseURL+"/init", wrapper.InitSandbox) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/metrics", wrapper.GetMetrics) diff --git a/packages/orchestrator/internal/sandbox/envd/envd.gen.go b/packages/orchestrator/internal/sandbox/envd/envd.gen.go index 73d03e999b..905c14728b 100644 --- a/packages/orchestrator/internal/sandbox/envd/envd.gen.go +++ b/packages/orchestrator/internal/sandbox/envd/envd.gen.go @@ -10,7 +10,7 @@ import ( ) const ( - AccessTokenAuthScopes = "AccessTokenAuth.Scopes" + SandboxAccessTokenAuthScopes = "SandboxAccessTokenAuth.Scopes" ) // Defines values for EntryInfoType. @@ -62,17 +62,26 @@ type Metrics struct { // MemTotal Total virtual memory in bytes MemTotal int `json:"mem_total,omitempty"` + // MemTotalMib Total virtual memory in MiB + MemTotalMib int `json:"mem_total_mib,omitempty"` + // MemUsed Used virtual memory in bytes MemUsed int `json:"mem_used,omitempty"` + // MemUsedMib Used virtual memory in MiB + MemUsedMib int `json:"mem_used_mib,omitempty"` + // Ts Unix timestamp in UTC for current sandbox time Ts int64 `json:"ts,omitempty"` } -// VolumeMount Volume +// VolumeMount NFS volume mount configuration type VolumeMount struct { + // NfsTarget NFS server target address NfsTarget string `json:"nfs_target"` - Path string `json:"path"` + + // Path Mount path inside the sandbox + Path string `json:"path"` } // FilePath defines model for FilePath. @@ -99,49 +108,52 @@ type InvalidPath = Error // InvalidUser defines model for InvalidUser. type InvalidUser = Error +// NotAcceptable defines model for NotAcceptable. +type NotAcceptable = Error + // NotEnoughDiskSpace defines model for NotEnoughDiskSpace. type NotEnoughDiskSpace = Error // UploadSuccess defines model for UploadSuccess. type UploadSuccess = []EntryInfo -// GetFilesParams defines parameters for GetFiles. -type GetFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. - Path FilePath `form:"path,omitempty" json:"path,omitempty"` +// DownloadFileParams defines parameters for DownloadFile. +type DownloadFileParams struct { + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). + Path FilePath `form:"path" json:"path"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// PostFilesMultipartBody defines parameters for PostFiles. -type PostFilesMultipartBody struct { +// UploadFileMultipartBody defines parameters for UploadFile. +type UploadFileMultipartBody struct { File openapi_types.File `json:"file,omitempty"` } -// PostFilesParams defines parameters for PostFiles. -type PostFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. - Path FilePath `form:"path,omitempty" json:"path,omitempty"` +// UploadFileParams defines parameters for UploadFile. +type UploadFileParams struct { + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). + Path FilePath `form:"path" json:"path"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// PostInitJSONBody defines parameters for PostInit. -type PostInitJSONBody struct { +// InitSandboxJSONBody defines parameters for InitSandbox. +type InitSandboxJSONBody struct { // AccessToken Access token for secure access to envd service AccessToken SecureToken `json:"accessToken,omitempty"` @@ -162,8 +174,8 @@ type PostInitJSONBody struct { VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` } -// PostFilesMultipartRequestBody defines body for PostFiles for multipart/form-data ContentType. -type PostFilesMultipartRequestBody PostFilesMultipartBody +// UploadFileMultipartRequestBody defines body for UploadFile for multipart/form-data ContentType. +type UploadFileMultipartRequestBody UploadFileMultipartBody -// PostInitJSONRequestBody defines body for PostInit for application/json ContentType. -type PostInitJSONRequestBody PostInitJSONBody +// InitSandboxJSONRequestBody defines body for InitSandbox for application/json ContentType. +type InitSandboxJSONRequestBody InitSandboxJSONBody diff --git a/tests/integration/internal/envd/generated.go b/tests/integration/internal/envd/generated.go index c0461908a3..c0933ce8a4 100644 --- a/tests/integration/internal/envd/generated.go +++ b/tests/integration/internal/envd/generated.go @@ -19,7 +19,7 @@ import ( ) const ( - AccessTokenAuthScopes = "AccessTokenAuth.Scopes" + SandboxAccessTokenAuthScopes = "SandboxAccessTokenAuth.Scopes" ) // Defines values for EntryInfoType. @@ -71,17 +71,26 @@ type Metrics struct { // MemTotal Total virtual memory in bytes MemTotal *int `json:"mem_total,omitempty"` + // MemTotalMib Total virtual memory in MiB + MemTotalMib *int `json:"mem_total_mib,omitempty"` + // MemUsed Used virtual memory in bytes MemUsed *int `json:"mem_used,omitempty"` + // MemUsedMib Used virtual memory in MiB + MemUsedMib *int `json:"mem_used_mib,omitempty"` + // Ts Unix timestamp in UTC for current sandbox time Ts *int64 `json:"ts,omitempty"` } -// VolumeMount Volume +// VolumeMount NFS volume mount configuration type VolumeMount struct { + // NfsTarget NFS server target address NfsTarget string `json:"nfs_target"` - Path string `json:"path"` + + // Path Mount path inside the sandbox + Path string `json:"path"` } // FilePath defines model for FilePath. @@ -108,49 +117,52 @@ type InvalidPath = Error // InvalidUser defines model for InvalidUser. type InvalidUser = Error +// NotAcceptable defines model for NotAcceptable. +type NotAcceptable = Error + // NotEnoughDiskSpace defines model for NotEnoughDiskSpace. type NotEnoughDiskSpace = Error // UploadSuccess defines model for UploadSuccess. type UploadSuccess = []EntryInfo -// GetFilesParams defines parameters for GetFiles. -type GetFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. - Path *FilePath `form:"path,omitempty" json:"path,omitempty"` +// DownloadFileParams defines parameters for DownloadFile. +type DownloadFileParams struct { + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). + Path FilePath `form:"path" json:"path"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// PostFilesMultipartBody defines parameters for PostFiles. -type PostFilesMultipartBody struct { +// UploadFileMultipartBody defines parameters for UploadFile. +type UploadFileMultipartBody struct { File *openapi_types.File `json:"file,omitempty"` } -// PostFilesParams defines parameters for PostFiles. -type PostFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. - Path *FilePath `form:"path,omitempty" json:"path,omitempty"` +// UploadFileParams defines parameters for UploadFile. +type UploadFileParams struct { + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). + Path FilePath `form:"path" json:"path"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// PostInitJSONBody defines parameters for PostInit. -type PostInitJSONBody struct { +// InitSandboxJSONBody defines parameters for InitSandbox. +type InitSandboxJSONBody struct { // AccessToken Access token for secure access to envd service AccessToken *SecureToken `json:"accessToken,omitempty"` @@ -171,11 +183,11 @@ type PostInitJSONBody struct { VolumeMounts *[]VolumeMount `json:"volumeMounts,omitempty"` } -// PostFilesMultipartRequestBody defines body for PostFiles for multipart/form-data ContentType. -type PostFilesMultipartRequestBody PostFilesMultipartBody +// UploadFileMultipartRequestBody defines body for UploadFile for multipart/form-data ContentType. +type UploadFileMultipartRequestBody UploadFileMultipartBody -// PostInitJSONRequestBody defines body for PostInit for application/json ContentType. -type PostInitJSONRequestBody PostInitJSONBody +// InitSandboxJSONRequestBody defines body for InitSandbox for application/json ContentType. +type InitSandboxJSONRequestBody InitSandboxJSONBody // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -250,29 +262,29 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { - // GetEnvs request - GetEnvs(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetEnvVars request + GetEnvVars(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetFiles request - GetFiles(ctx context.Context, params *GetFilesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // DownloadFile request + DownloadFile(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // PostFilesWithBody request with any body - PostFilesWithBody(ctx context.Context, params *PostFilesParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // UploadFileWithBody request with any body + UploadFileWithBody(ctx context.Context, params *UploadFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) // GetHealth request GetHealth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // PostInitWithBody request with any body - PostInitWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // InitSandboxWithBody request with any body + InitSandboxWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostInit(ctx context.Context, body PostInitJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + InitSandbox(ctx context.Context, body InitSandboxJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetMetrics request GetMetrics(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) } -func (c *Client) GetEnvs(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetEnvsRequest(c.Server) +func (c *Client) GetEnvVars(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetEnvVarsRequest(c.Server) if err != nil { return nil, err } @@ -283,8 +295,8 @@ func (c *Client) GetEnvs(ctx context.Context, reqEditors ...RequestEditorFn) (*h return c.Client.Do(req) } -func (c *Client) GetFiles(ctx context.Context, params *GetFilesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetFilesRequest(c.Server, params) +func (c *Client) DownloadFile(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDownloadFileRequest(c.Server, params) if err != nil { return nil, err } @@ -295,8 +307,8 @@ func (c *Client) GetFiles(ctx context.Context, params *GetFilesParams, reqEditor return c.Client.Do(req) } -func (c *Client) PostFilesWithBody(ctx context.Context, params *PostFilesParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostFilesRequestWithBody(c.Server, params, contentType, body) +func (c *Client) UploadFileWithBody(ctx context.Context, params *UploadFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUploadFileRequestWithBody(c.Server, params, contentType, body) if err != nil { return nil, err } @@ -319,8 +331,8 @@ func (c *Client) GetHealth(ctx context.Context, reqEditors ...RequestEditorFn) ( return c.Client.Do(req) } -func (c *Client) PostInitWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostInitRequestWithBody(c.Server, contentType, body) +func (c *Client) InitSandboxWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewInitSandboxRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -331,8 +343,8 @@ func (c *Client) PostInitWithBody(ctx context.Context, contentType string, body return c.Client.Do(req) } -func (c *Client) PostInit(ctx context.Context, body PostInitJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostInitRequest(c.Server, body) +func (c *Client) InitSandbox(ctx context.Context, body InitSandboxJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewInitSandboxRequest(c.Server, body) if err != nil { return nil, err } @@ -355,8 +367,8 @@ func (c *Client) GetMetrics(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -// NewGetEnvsRequest generates requests for GetEnvs -func NewGetEnvsRequest(server string) (*http.Request, error) { +// NewGetEnvVarsRequest generates requests for GetEnvVars +func NewGetEnvVarsRequest(server string) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -382,8 +394,8 @@ func NewGetEnvsRequest(server string) (*http.Request, error) { return req, nil } -// NewGetFilesRequest generates requests for GetFiles -func NewGetFilesRequest(server string, params *GetFilesParams) (*http.Request, error) { +// NewDownloadFileRequest generates requests for DownloadFile +func NewDownloadFileRequest(server string, params *DownloadFileParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -404,20 +416,16 @@ func NewGetFilesRequest(server string, params *GetFilesParams) (*http.Request, e if params != nil { queryValues := queryURL.Query() - if params.Path != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } if params.Username != nil { @@ -479,8 +487,8 @@ func NewGetFilesRequest(server string, params *GetFilesParams) (*http.Request, e return req, nil } -// NewPostFilesRequestWithBody generates requests for PostFiles with any type of body -func NewPostFilesRequestWithBody(server string, params *PostFilesParams, contentType string, body io.Reader) (*http.Request, error) { +// NewUploadFileRequestWithBody generates requests for UploadFile with any type of body +func NewUploadFileRequestWithBody(server string, params *UploadFileParams, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -501,20 +509,16 @@ func NewPostFilesRequestWithBody(server string, params *PostFilesParams, content if params != nil { queryValues := queryURL.Query() - if params.Path != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } if params.Username != nil { @@ -605,19 +609,19 @@ func NewGetHealthRequest(server string) (*http.Request, error) { return req, nil } -// NewPostInitRequest calls the generic PostInit builder with application/json body -func NewPostInitRequest(server string, body PostInitJSONRequestBody) (*http.Request, error) { +// NewInitSandboxRequest calls the generic InitSandbox builder with application/json body +func NewInitSandboxRequest(server string, body InitSandboxJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostInitRequestWithBody(server, "application/json", bodyReader) + return NewInitSandboxRequestWithBody(server, "application/json", bodyReader) } -// NewPostInitRequestWithBody generates requests for PostInit with any type of body -func NewPostInitRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewInitSandboxRequestWithBody generates requests for InitSandbox with any type of body +func NewInitSandboxRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -715,35 +719,35 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // GetEnvsWithResponse request - GetEnvsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetEnvsResponse, error) + // GetEnvVarsWithResponse request + GetEnvVarsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetEnvVarsResponse, error) - // GetFilesWithResponse request - GetFilesWithResponse(ctx context.Context, params *GetFilesParams, reqEditors ...RequestEditorFn) (*GetFilesResponse, error) + // DownloadFileWithResponse request + DownloadFileWithResponse(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*DownloadFileResponse, error) - // PostFilesWithBodyWithResponse request with any body - PostFilesWithBodyWithResponse(ctx context.Context, params *PostFilesParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostFilesResponse, error) + // UploadFileWithBodyWithResponse request with any body + UploadFileWithBodyWithResponse(ctx context.Context, params *UploadFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadFileResponse, error) // GetHealthWithResponse request GetHealthWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetHealthResponse, error) - // PostInitWithBodyWithResponse request with any body - PostInitWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostInitResponse, error) + // InitSandboxWithBodyWithResponse request with any body + InitSandboxWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*InitSandboxResponse, error) - PostInitWithResponse(ctx context.Context, body PostInitJSONRequestBody, reqEditors ...RequestEditorFn) (*PostInitResponse, error) + InitSandboxWithResponse(ctx context.Context, body InitSandboxJSONRequestBody, reqEditors ...RequestEditorFn) (*InitSandboxResponse, error) // GetMetricsWithResponse request GetMetricsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetMetricsResponse, error) } -type GetEnvsResponse struct { +type GetEnvVarsResponse struct { Body []byte HTTPResponse *http.Response JSON200 *EnvVars } // Status returns HTTPResponse.Status -func (r GetEnvsResponse) Status() string { +func (r GetEnvVarsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -751,24 +755,25 @@ func (r GetEnvsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetEnvsResponse) StatusCode() int { +func (r GetEnvVarsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetFilesResponse struct { +type DownloadFileResponse struct { Body []byte HTTPResponse *http.Response JSON400 *InvalidPath JSON401 *InvalidUser JSON404 *FileNotFound + JSON406 *NotAcceptable JSON500 *InternalServerError } // Status returns HTTPResponse.Status -func (r GetFilesResponse) Status() string { +func (r DownloadFileResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -776,14 +781,14 @@ func (r GetFilesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetFilesResponse) StatusCode() int { +func (r DownloadFileResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type PostFilesResponse struct { +type UploadFileResponse struct { Body []byte HTTPResponse *http.Response JSON200 *UploadSuccess @@ -794,7 +799,7 @@ type PostFilesResponse struct { } // Status returns HTTPResponse.Status -func (r PostFilesResponse) Status() string { +func (r UploadFileResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -802,7 +807,7 @@ func (r PostFilesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r PostFilesResponse) StatusCode() int { +func (r UploadFileResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -830,13 +835,13 @@ func (r GetHealthResponse) StatusCode() int { return 0 } -type PostInitResponse struct { +type InitSandboxResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r PostInitResponse) Status() string { +func (r InitSandboxResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -844,7 +849,7 @@ func (r PostInitResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r PostInitResponse) StatusCode() int { +func (r InitSandboxResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -873,31 +878,31 @@ func (r GetMetricsResponse) StatusCode() int { return 0 } -// GetEnvsWithResponse request returning *GetEnvsResponse -func (c *ClientWithResponses) GetEnvsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetEnvsResponse, error) { - rsp, err := c.GetEnvs(ctx, reqEditors...) +// GetEnvVarsWithResponse request returning *GetEnvVarsResponse +func (c *ClientWithResponses) GetEnvVarsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetEnvVarsResponse, error) { + rsp, err := c.GetEnvVars(ctx, reqEditors...) if err != nil { return nil, err } - return ParseGetEnvsResponse(rsp) + return ParseGetEnvVarsResponse(rsp) } -// GetFilesWithResponse request returning *GetFilesResponse -func (c *ClientWithResponses) GetFilesWithResponse(ctx context.Context, params *GetFilesParams, reqEditors ...RequestEditorFn) (*GetFilesResponse, error) { - rsp, err := c.GetFiles(ctx, params, reqEditors...) +// DownloadFileWithResponse request returning *DownloadFileResponse +func (c *ClientWithResponses) DownloadFileWithResponse(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*DownloadFileResponse, error) { + rsp, err := c.DownloadFile(ctx, params, reqEditors...) if err != nil { return nil, err } - return ParseGetFilesResponse(rsp) + return ParseDownloadFileResponse(rsp) } -// PostFilesWithBodyWithResponse request with arbitrary body returning *PostFilesResponse -func (c *ClientWithResponses) PostFilesWithBodyWithResponse(ctx context.Context, params *PostFilesParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostFilesResponse, error) { - rsp, err := c.PostFilesWithBody(ctx, params, contentType, body, reqEditors...) +// UploadFileWithBodyWithResponse request with arbitrary body returning *UploadFileResponse +func (c *ClientWithResponses) UploadFileWithBodyWithResponse(ctx context.Context, params *UploadFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadFileResponse, error) { + rsp, err := c.UploadFileWithBody(ctx, params, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParsePostFilesResponse(rsp) + return ParseUploadFileResponse(rsp) } // GetHealthWithResponse request returning *GetHealthResponse @@ -909,21 +914,21 @@ func (c *ClientWithResponses) GetHealthWithResponse(ctx context.Context, reqEdit return ParseGetHealthResponse(rsp) } -// PostInitWithBodyWithResponse request with arbitrary body returning *PostInitResponse -func (c *ClientWithResponses) PostInitWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostInitResponse, error) { - rsp, err := c.PostInitWithBody(ctx, contentType, body, reqEditors...) +// InitSandboxWithBodyWithResponse request with arbitrary body returning *InitSandboxResponse +func (c *ClientWithResponses) InitSandboxWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*InitSandboxResponse, error) { + rsp, err := c.InitSandboxWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParsePostInitResponse(rsp) + return ParseInitSandboxResponse(rsp) } -func (c *ClientWithResponses) PostInitWithResponse(ctx context.Context, body PostInitJSONRequestBody, reqEditors ...RequestEditorFn) (*PostInitResponse, error) { - rsp, err := c.PostInit(ctx, body, reqEditors...) +func (c *ClientWithResponses) InitSandboxWithResponse(ctx context.Context, body InitSandboxJSONRequestBody, reqEditors ...RequestEditorFn) (*InitSandboxResponse, error) { + rsp, err := c.InitSandbox(ctx, body, reqEditors...) if err != nil { return nil, err } - return ParsePostInitResponse(rsp) + return ParseInitSandboxResponse(rsp) } // GetMetricsWithResponse request returning *GetMetricsResponse @@ -935,15 +940,15 @@ func (c *ClientWithResponses) GetMetricsWithResponse(ctx context.Context, reqEdi return ParseGetMetricsResponse(rsp) } -// ParseGetEnvsResponse parses an HTTP response from a GetEnvsWithResponse call -func ParseGetEnvsResponse(rsp *http.Response) (*GetEnvsResponse, error) { +// ParseGetEnvVarsResponse parses an HTTP response from a GetEnvVarsWithResponse call +func ParseGetEnvVarsResponse(rsp *http.Response) (*GetEnvVarsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetEnvsResponse{ + response := &GetEnvVarsResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -961,15 +966,15 @@ func ParseGetEnvsResponse(rsp *http.Response) (*GetEnvsResponse, error) { return response, nil } -// ParseGetFilesResponse parses an HTTP response from a GetFilesWithResponse call -func ParseGetFilesResponse(rsp *http.Response) (*GetFilesResponse, error) { +// ParseDownloadFileResponse parses an HTTP response from a DownloadFileWithResponse call +func ParseDownloadFileResponse(rsp *http.Response) (*DownloadFileResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetFilesResponse{ + response := &DownloadFileResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -996,6 +1001,13 @@ func ParseGetFilesResponse(rsp *http.Response) (*GetFilesResponse, error) { } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 406: + var dest NotAcceptable + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON406 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -1008,15 +1020,15 @@ func ParseGetFilesResponse(rsp *http.Response) (*GetFilesResponse, error) { return response, nil } -// ParsePostFilesResponse parses an HTTP response from a PostFilesWithResponse call -func ParsePostFilesResponse(rsp *http.Response) (*PostFilesResponse, error) { +// ParseUploadFileResponse parses an HTTP response from a UploadFileWithResponse call +func ParseUploadFileResponse(rsp *http.Response) (*UploadFileResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &PostFilesResponse{ + response := &UploadFileResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -1078,15 +1090,15 @@ func ParseGetHealthResponse(rsp *http.Response) (*GetHealthResponse, error) { return response, nil } -// ParsePostInitResponse parses an HTTP response from a PostInitWithResponse call -func ParsePostInitResponse(rsp *http.Response) (*PostInitResponse, error) { +// ParseInitSandboxResponse parses an HTTP response from a InitSandboxWithResponse call +func ParseInitSandboxResponse(rsp *http.Response) (*InitSandboxResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &PostInitResponse{ + response := &InitSandboxResponse{ Body: bodyBytes, HTTPResponse: rsp, } From d6201552b6685b1f700d76ace281345241109399 Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Thu, 19 Feb 2026 17:00:09 -0800 Subject: [PATCH 03/11] fix: address PR review comments on generate_openapi script - Fix temp file leak: use try/finally for Dockerfile cleanup on build failure - Pin protoc-gen-connect-openapi to v0.25.3 (was @latest) - Deduplicate global security entries during spec merge - Fix substring collision in orphan schema detection (Foo vs FooBar) - Keep /volumes endpoints in generated docs (remove from excluded prefixes) --- scripts/generate_openapi.py | 50 +++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/scripts/generate_openapi.py b/scripts/generate_openapi.py index 81b1e22927..52fbb242a3 100644 --- a/scripts/generate_openapi.py +++ b/scripts/generate_openapi.py @@ -51,7 +51,7 @@ FROM golang:1.25-alpine RUN apk add --no-cache git RUN go install github.com/bufbuild/buf/cmd/buf@v1.50.0 -RUN go install github.com/sudorandom/protoc-gen-connect-openapi@latest +RUN go install github.com/sudorandom/protoc-gen-connect-openapi@v0.25.3 ENV PATH="/go/bin:${PATH}" """ @@ -256,13 +256,15 @@ def docker_build_image() -> None: print("==> Building Docker image") with tempfile.NamedTemporaryFile(mode="w", suffix=".Dockerfile", delete=False) as f: f.write(DOCKERFILE) - f.flush() + dockerfile_path = f.name + try: subprocess.run( - ["docker", "build", "-t", DOCKER_IMAGE, "-f", f.name, "."], + ["docker", "build", "-t", DOCKER_IMAGE, "-f", dockerfile_path, "."], check=True, cwd=REPO_ROOT, ) - os.unlink(f.name) + finally: + os.unlink(dockerfile_path) def docker_generate_specs() -> list[str]: @@ -351,7 +353,10 @@ def merge_specs(raw_docs: list[str]) -> dict[str, Any]: merged.setdefault("tags", []).extend(doc["tags"]) if "security" in doc: - merged.setdefault("security", []).extend(doc["security"]) + existing = merged.setdefault("security", []) + for entry in doc["security"]: + if entry not in existing: + existing.append(entry) return merged @@ -444,13 +449,13 @@ def _has_admin_token_security(path_item: dict[str, Any]) -> bool: def filter_paths(spec: dict[str, Any]) -> None: """Clean up paths that should not appear in the public spec. - - Removes volume, access-token, and api-key endpoints + - Removes access-token and api-key endpoints - Removes endpoints using AdminToken auth - Strips Supabase auth entries from all operations - Removes Supabase and AdminToken securityScheme definitions """ # Remove excluded paths - excluded_prefixes = ("/volumes", "/access-tokens", "/api-keys") + excluded_prefixes = ("/access-tokens", "/api-keys") excluded_exact = {"/v2/sandboxes/{sandboxID}/logs", "/init"} to_remove = [ p for p in spec["paths"] @@ -500,21 +505,22 @@ def remove_orphaned_schemas(spec: dict[str, Any]) -> None: orphaned = [] for name in list(schemas.keys()): - ref_pattern = f"schemas/{name}" - # Referenced from paths/responses/params or from other schemas - if ref_pattern not in spec_text and ref_pattern not in schema_text.replace( - f"schemas/{name}:", "" # exclude self-definition line - ): - # Double-check: not referenced by any other schema - used = False - for other_name, other_schema in schemas.items(): - if other_name == name: - continue - if ref_pattern in yaml.dump(other_schema, default_flow_style=False): - used = True - break - if not used: - orphaned.append(name) + # Use exact ref pattern to avoid substring collisions + # (e.g. "schemas/Foo" matching inside "schemas/FooBar") + ref_pattern = f"schemas/{name}'" + # Referenced from paths/responses/params + if ref_pattern in spec_text: + continue + # Referenced from other schemas (exclude self-definition) + used = False + for other_name, other_schema in schemas.items(): + if other_name == name: + continue + if ref_pattern in yaml.dump(other_schema, default_flow_style=False): + used = True + break + if not used: + orphaned.append(name) if not orphaned: break From 2a080e52952c874e13153a0bc8741301bbb82345 Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Thu, 19 Feb 2026 17:11:39 -0800 Subject: [PATCH 04/11] fix: update Go code to match generated types from new operationIds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The operationId additions in envd.yaml changed the generated Go type and method names. Update all references across envd, orchestrator, and integration tests: - GetFilesParams → DownloadFileParams - PostFilesParams → UploadFileParams - PostInitJSONBody → InitSandboxJSONBody - GetFiles → DownloadFile, PostFiles → UploadFile - GetEnvs → GetEnvVars, PostInit → InitSandbox - Path field is now non-pointer (required in spec) - PostFilesWithBodyWithResponse → UploadFileWithBodyWithResponse - GetFilesWithResponse → DownloadFileWithResponse --- packages/envd/internal/api/download.go | 7 +-- packages/envd/internal/api/download_test.go | 42 ++++++++--------- packages/envd/internal/api/envs.go | 2 +- packages/envd/internal/api/init.go | 6 +-- packages/envd/internal/api/init_test.go | 14 +++--- packages/envd/internal/api/upload.go | 15 +++--- .../orchestrator/internal/sandbox/envd.go | 2 +- .../api/sandboxes/sandbox_auto_pause_test.go | 12 ++--- .../tests/api/templates/template_tags_test.go | 8 ++-- .../internal/tests/api/volumes/crud_test.go | 12 ++--- .../internal/tests/envd/auth_test.go | 8 ++-- .../internal/tests/envd/hyperloop_test.go | 12 ++--- .../internal/tests/envd/signatures_test.go | 46 +++++++++---------- .../integration/internal/utils/filesystem.go | 4 +- 14 files changed, 92 insertions(+), 98 deletions(-) diff --git a/packages/envd/internal/api/download.go b/packages/envd/internal/api/download.go index 8b965bd817..3e643b17cf 100644 --- a/packages/envd/internal/api/download.go +++ b/packages/envd/internal/api/download.go @@ -16,16 +16,13 @@ import ( "github.com/e2b-dev/infra/packages/envd/internal/permissions" ) -func (a *API) GetFiles(w http.ResponseWriter, r *http.Request, params GetFilesParams) { +func (a *API) DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) { defer r.Body.Close() var errorCode int var errMsg error - var path string - if params.Path != nil { - path = *params.Path - } + path := string(params.Path) operationID := logs.AssignOperationID() diff --git a/packages/envd/internal/api/download_test.go b/packages/envd/internal/api/download_test.go index fabcb55a57..9caec483f1 100644 --- a/packages/envd/internal/api/download_test.go +++ b/packages/envd/internal/api/download_test.go @@ -102,11 +102,11 @@ func TestGetFilesContentDisposition(t *testing.T) { w := httptest.NewRecorder() // Call the handler - params := GetFilesParams{ - Path: &tempFile, + params := DownloadFileParams{ + Path: FilePath(tempFile), Username: ¤tUser.Username, } - api.GetFiles(w, req, params) + api.DownloadFile(w, req, params) // Check response resp := w.Result() @@ -151,11 +151,11 @@ func TestGetFilesContentDispositionWithNestedPath(t *testing.T) { w := httptest.NewRecorder() // Call the handler - params := GetFilesParams{ - Path: &tempFile, + params := DownloadFileParams{ + Path: FilePath(tempFile), Username: ¤tUser.Username, } - api.GetFiles(w, req, params) + api.DownloadFile(w, req, params) // Check response resp := w.Result() @@ -196,11 +196,11 @@ func TestGetFiles_GzipEncoding_ExplicitIdentityOffWithRange(t *testing.T) { w := httptest.NewRecorder() // Call the handler - params := GetFilesParams{ - Path: &tempFile, + params := DownloadFileParams{ + Path: FilePath(tempFile), Username: ¤tUser.Username, } - api.GetFiles(w, req, params) + api.DownloadFile(w, req, params) // Check response resp := w.Result() @@ -234,11 +234,11 @@ func TestGetFiles_GzipDownload(t *testing.T) { req.Header.Set("Accept-Encoding", "gzip") w := httptest.NewRecorder() - params := GetFilesParams{ - Path: &tempFile, + params := DownloadFileParams{ + Path: FilePath(tempFile), Username: ¤tUser.Username, } - api.GetFiles(w, req, params) + api.DownloadFile(w, req, params) resp := w.Result() defer resp.Body.Close() @@ -300,11 +300,11 @@ func TestPostFiles_GzipUpload(t *testing.T) { req.Header.Set("Content-Encoding", "gzip") w := httptest.NewRecorder() - params := PostFilesParams{ - Path: &destPath, + params := UploadFileParams{ + Path: FilePath(destPath), Username: ¤tUser.Username, } - api.PostFiles(w, req, params) + api.UploadFile(w, req, params) resp := w.Result() defer resp.Body.Close() @@ -360,11 +360,11 @@ func TestGzipUploadThenGzipDownload(t *testing.T) { uploadReq.Header.Set("Content-Encoding", "gzip") uploadW := httptest.NewRecorder() - uploadParams := PostFilesParams{ - Path: &destPath, + uploadParams := UploadFileParams{ + Path: FilePath(destPath), Username: ¤tUser.Username, } - api.PostFiles(uploadW, uploadReq, uploadParams) + api.UploadFile(uploadW, uploadReq, uploadParams) uploadResp := uploadW.Result() defer uploadResp.Body.Close() @@ -377,11 +377,11 @@ func TestGzipUploadThenGzipDownload(t *testing.T) { downloadReq.Header.Set("Accept-Encoding", "gzip") downloadW := httptest.NewRecorder() - downloadParams := GetFilesParams{ - Path: &destPath, + downloadParams := DownloadFileParams{ + Path: FilePath(destPath), Username: ¤tUser.Username, } - api.GetFiles(downloadW, downloadReq, downloadParams) + api.DownloadFile(downloadW, downloadReq, downloadParams) downloadResp := downloadW.Result() defer downloadResp.Body.Close() diff --git a/packages/envd/internal/api/envs.go b/packages/envd/internal/api/envs.go index 28d34da3aa..e3b3686749 100644 --- a/packages/envd/internal/api/envs.go +++ b/packages/envd/internal/api/envs.go @@ -7,7 +7,7 @@ import ( "github.com/e2b-dev/infra/packages/envd/internal/logs" ) -func (a *API) GetEnvs(w http.ResponseWriter, _ *http.Request) { +func (a *API) GetEnvVars(w http.ResponseWriter, _ *http.Request) { operationID := logs.AssignOperationID() a.logger.Debug().Str(string(logs.OperationIDKey), operationID).Msg("Getting env vars") diff --git a/packages/envd/internal/api/init.go b/packages/envd/internal/api/init.go index 29a1e7e79e..41d3b1ef3d 100644 --- a/packages/envd/internal/api/init.go +++ b/packages/envd/internal/api/init.go @@ -91,7 +91,7 @@ func (a *API) checkMMDSHash(ctx context.Context, requestToken *SecureToken) (boo return keys.HashAccessTokenBytes(tokenBytes) == mmdsHash, true } -func (a *API) PostInit(w http.ResponseWriter, r *http.Request) { +func (a *API) InitSandbox(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() ctx := r.Context() @@ -111,7 +111,7 @@ func (a *API) PostInit(w http.ResponseWriter, r *http.Request) { return } - var initRequest PostInitJSONBody + var initRequest InitSandboxJSONBody if len(body) > 0 { err = json.Unmarshal(body, &initRequest) if err != nil { @@ -160,7 +160,7 @@ func (a *API) PostInit(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } -func (a *API) SetData(ctx context.Context, logger zerolog.Logger, data PostInitJSONBody) error { +func (a *API) SetData(ctx context.Context, logger zerolog.Logger, data InitSandboxJSONBody) error { // Validate access token before proceeding with any action // The request must provide a token that is either: // 1. Matches the existing access token (if set), OR diff --git a/packages/envd/internal/api/init_test.go b/packages/envd/internal/api/init_test.go index 9877104e09..a35763a76e 100644 --- a/packages/envd/internal/api/init_test.go +++ b/packages/envd/internal/api/init_test.go @@ -453,7 +453,7 @@ func TestSetData(t *testing.T) { mmdsClient := &mockMMDSClient{hash: tt.mmdsHash, err: tt.mmdsErr} api := newTestAPI(tt.existingToken, mmdsClient) - data := PostInitJSONBody{ + data := InitSandboxJSONBody{ AccessToken: tt.requestToken, } @@ -481,7 +481,7 @@ func TestSetData(t *testing.T) { api := newTestAPI(nil, mmdsClient) envVars := EnvVars{"FOO": "bar", "BAZ": "qux"} - data := PostInitJSONBody{ + data := InitSandboxJSONBody{ EnvVars: &envVars, } @@ -501,7 +501,7 @@ func TestSetData(t *testing.T) { mmdsClient := &mockMMDSClient{hash: "", err: assert.AnError} api := newTestAPI(nil, mmdsClient) - data := PostInitJSONBody{ + data := InitSandboxJSONBody{ DefaultUser: utilsShared.ToPtr("testuser"), } @@ -517,7 +517,7 @@ func TestSetData(t *testing.T) { api := newTestAPI(nil, mmdsClient) api.defaults.User = "original" - data := PostInitJSONBody{ + data := InitSandboxJSONBody{ DefaultUser: utilsShared.ToPtr(""), } @@ -532,7 +532,7 @@ func TestSetData(t *testing.T) { mmdsClient := &mockMMDSClient{hash: "", err: assert.AnError} api := newTestAPI(nil, mmdsClient) - data := PostInitJSONBody{ + data := InitSandboxJSONBody{ DefaultWorkdir: utilsShared.ToPtr("/home/user"), } @@ -550,7 +550,7 @@ func TestSetData(t *testing.T) { originalWorkdir := "/original" api.defaults.Workdir = &originalWorkdir - data := PostInitJSONBody{ + data := InitSandboxJSONBody{ DefaultWorkdir: utilsShared.ToPtr(""), } @@ -567,7 +567,7 @@ func TestSetData(t *testing.T) { api := newTestAPI(nil, mmdsClient) envVars := EnvVars{"KEY": "value"} - data := PostInitJSONBody{ + data := InitSandboxJSONBody{ AccessToken: secureTokenPtr("token"), DefaultUser: utilsShared.ToPtr("user"), DefaultWorkdir: utilsShared.ToPtr("/workdir"), diff --git a/packages/envd/internal/api/upload.go b/packages/envd/internal/api/upload.go index fb6868831d..621daa14c9 100644 --- a/packages/envd/internal/api/upload.go +++ b/packages/envd/internal/api/upload.go @@ -107,11 +107,11 @@ func processFile(r *http.Request, path string, part io.Reader, uid, gid int, log return http.StatusNoContent, nil } -func resolvePath(part *multipart.Part, paths *UploadSuccess, u *user.User, defaultPath *string, params PostFilesParams) (string, error) { +func resolvePath(part *multipart.Part, paths *UploadSuccess, u *user.User, defaultPath *string, params UploadFileParams) (string, error) { var pathToResolve string - if params.Path != nil { - pathToResolve = *params.Path + if params.Path != "" { + pathToResolve = string(params.Path) } else { var err error customPart := utils.NewCustomPart(part) @@ -148,7 +148,7 @@ func resolvePath(part *multipart.Part, paths *UploadSuccess, u *user.User, defau return filePath, nil } -func (a *API) handlePart(r *http.Request, part *multipart.Part, paths UploadSuccess, u *user.User, uid, gid int, operationID string, params PostFilesParams) (*EntryInfo, int, error) { +func (a *API) handlePart(r *http.Request, part *multipart.Part, paths UploadSuccess, u *user.User, uid, gid int, operationID string, params UploadFileParams) (*EntryInfo, int, error) { defer part.Close() if part.FormName() != "file" { @@ -178,7 +178,7 @@ func (a *API) handlePart(r *http.Request, part *multipart.Part, paths UploadSucc }, http.StatusOK, nil } -func (a *API) PostFiles(w http.ResponseWriter, r *http.Request, params PostFilesParams) { +func (a *API) UploadFile(w http.ResponseWriter, r *http.Request, params UploadFileParams) { // Capture original body to ensure it's always closed originalBody := r.Body defer originalBody.Close() @@ -186,10 +186,7 @@ func (a *API) PostFiles(w http.ResponseWriter, r *http.Request, params PostFiles var errorCode int var errMsg error - var path string - if params.Path != nil { - path = *params.Path - } + path := string(params.Path) operationID := logs.AssignOperationID() diff --git a/packages/orchestrator/internal/sandbox/envd.go b/packages/orchestrator/internal/sandbox/envd.go index 4b5de9147a..de5a30e099 100644 --- a/packages/orchestrator/internal/sandbox/envd.go +++ b/packages/orchestrator/internal/sandbox/envd.go @@ -35,7 +35,7 @@ func (s *Sandbox) doRequestWithInfiniteRetries( ) (*http.Response, int64, error) { requestCount := int64(0) - jsonBody := &envd.PostInitJSONBody{ + jsonBody := &envd.InitSandboxJSONBody{ EnvVars: s.Config.Envd.Vars, HyperloopIP: s.config.NetworkConfig.OrchestratorInSandboxIPAddress, AccessToken: utils.DerefOrDefault(s.Config.Envd.AccessToken, ""), diff --git a/tests/integration/internal/tests/api/sandboxes/sandbox_auto_pause_test.go b/tests/integration/internal/tests/api/sandboxes/sandbox_auto_pause_test.go index e8502b56b6..a3b31495e2 100644 --- a/tests/integration/internal/tests/api/sandboxes/sandbox_auto_pause_test.go +++ b/tests/integration/internal/tests/api/sandboxes/sandbox_auto_pause_test.go @@ -88,10 +88,10 @@ func TestSandboxAutoPauseResumePersisted(t *testing.T) { require.NoError(t, err) // Check if the file is still there after resuming - fileResponse, err := envdClient.HTTPClient.GetFilesWithResponse( + fileResponse, err := envdClient.HTTPClient.DownloadFileWithResponse( t.Context(), - &envd.GetFilesParams{ - Path: &path, + &envd.DownloadFileParams{ + Path: path, Username: sharedUtils.ToPtr("user"), }, setup.WithSandbox(t, sbxId), @@ -128,10 +128,10 @@ func TestSandboxAutoPauseResumePersisted(t *testing.T) { assert.Equal(t, sbxResume.JSON201.SandboxID, sbxId) // Check if the file is still there after resuming - fileResponse, err = envdClient.HTTPClient.GetFilesWithResponse( + fileResponse, err = envdClient.HTTPClient.DownloadFileWithResponse( t.Context(), - &envd.GetFilesParams{ - Path: &path, + &envd.DownloadFileParams{ + Path: path, Username: sharedUtils.ToPtr("user"), }, setup.WithSandbox(t, sbxId), diff --git a/tests/integration/internal/tests/api/templates/template_tags_test.go b/tests/integration/internal/tests/api/templates/template_tags_test.go index 3e964dc66c..48f135adc5 100644 --- a/tests/integration/internal/tests/api/templates/template_tags_test.go +++ b/tests/integration/internal/tests/api/templates/template_tags_test.go @@ -550,9 +550,9 @@ func TestAssignmentOrderingLatestWins(t *testing.T) { // Read the version file from the sandbox to verify it's using the latest build envdClient := setup.GetEnvdClient(t, ctx) - fileResp, err := envdClient.HTTPClient.GetFilesWithResponse( + fileResp, err := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &versionFilePath, Username: utils.ToPtr("user")}, + &envd.DownloadFileParams{Path: versionFilePath, Username: utils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) require.NoError(t, err) @@ -619,9 +619,9 @@ func TestAssignmentOrderingAfterTagReassignment(t *testing.T) { // Read the version file from the sandbox to verify it's using the reassigned build envdClient := setup.GetEnvdClient(t, ctx) - fileResp, err := envdClient.HTTPClient.GetFilesWithResponse( + fileResp, err := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &versionFilePath, Username: utils.ToPtr("user")}, + &envd.DownloadFileParams{Path: versionFilePath, Username: utils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) require.NoError(t, err) diff --git a/tests/integration/internal/tests/api/volumes/crud_test.go b/tests/integration/internal/tests/api/volumes/crud_test.go index 14c3e3c681..7f123550ed 100644 --- a/tests/integration/internal/tests/api/volumes/crud_test.go +++ b/tests/integration/internal/tests/api/volumes/crud_test.go @@ -110,9 +110,9 @@ func TestVolumeRoundTrip(t *testing.T) { { ctx := t.Context() envdClient := setup.GetEnvdClient(t, ctx) - readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( + readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &filePath, Username: sharedutils.ToPtr("user")}, + &envd.DownloadFileParams{Path: filePath, Username: sharedutils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) require.NoError(t, readErr) @@ -166,9 +166,9 @@ func TestVolumeRoundTrip(t *testing.T) { { ctx := t.Context() envdClient := setup.GetEnvdClient(t, ctx) - readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( + readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &filePath, Username: sharedutils.ToPtr("user")}, + &envd.DownloadFileParams{Path: filePath, Username: sharedutils.ToPtr("user")}, setup.WithSandbox(t, sbx2.SandboxID), ) require.NoError(t, readErr) @@ -187,9 +187,9 @@ func TestVolumeRoundTrip(t *testing.T) { require.NoError(t, remErr) // verify it's gone - readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( + readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &filePath, Username: sharedutils.ToPtr("user")}, + &envd.DownloadFileParams{Path: filePath, Username: sharedutils.ToPtr("user")}, setup.WithSandbox(t, sbx2.SandboxID), ) require.NoError(t, readErr) diff --git a/tests/integration/internal/tests/envd/auth_test.go b/tests/integration/internal/tests/envd/auth_test.go index 32f23497b5..1f04aaae44 100644 --- a/tests/integration/internal/tests/envd/auth_test.go +++ b/tests/integration/internal/tests/envd/auth_test.go @@ -185,9 +185,9 @@ func TestAccessAuthorizedPathWithResumedSandboxWithValidAccessToken(t *testing.T require.Equal(t, http.StatusCreated, sbxResume.StatusCode()) // try to get the file with the valid access token - fileResponse, err := envdClient.HTTPClient.GetFilesWithResponse( + fileResponse, err := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &filePath, Username: sharedUtils.ToPtr("user")}, + &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.JSON201.SandboxID), setup.WithEnvdAccessToken(t, *sbxMeta.EnvdAccessToken), ) @@ -242,9 +242,9 @@ func TestAccessAuthorizedPathWithResumedSandboxWithoutAccessToken(t *testing.T) assert.Equal(t, http.StatusCreated, sbxResume.StatusCode()) // try to get the file with the without access token - fileResponse, err := envdClient.HTTPClient.GetFilesWithResponse( + fileResponse, err := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &filePath, Username: sharedUtils.ToPtr("user")}, + &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.JSON201.SandboxID), ) if err != nil { diff --git a/tests/integration/internal/tests/envd/hyperloop_test.go b/tests/integration/internal/tests/envd/hyperloop_test.go index c7972c1600..0d62fe1c11 100644 --- a/tests/integration/internal/tests/envd/hyperloop_test.go +++ b/tests/integration/internal/tests/envd/hyperloop_test.go @@ -29,9 +29,9 @@ func TestAccessingHyperloopServerViaIP(t *testing.T) { require.NoError(t, err, "Should be able to contact hyperloop server") readPath := "output.txt" - readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( + readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &readPath, Username: sharedUtils.ToPtr("user")}, + &envd.DownloadFileParams{Path: readPath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) @@ -54,9 +54,9 @@ func TestAccessingHyperloopServerViaDomain(t *testing.T) { require.NoError(t, err, "Should be able to contact hyperloop server") readPath := "output.txt" - readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( + readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &readPath, Username: sharedUtils.ToPtr("user")}, + &envd.DownloadFileParams{Path: readPath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) @@ -79,9 +79,9 @@ func TestAccessingHyperloopServerViaIPWithBlockedInternet(t *testing.T) { require.NoError(t, err, "Should be able to contact hyperloop server") readPath := "output.txt" - readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( + readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &readPath, Username: sharedUtils.ToPtr("user")}, + &envd.DownloadFileParams{Path: readPath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) diff --git a/tests/integration/internal/tests/envd/signatures_test.go b/tests/integration/internal/tests/envd/signatures_test.go index 309cb7b2e0..64469ec11e 100644 --- a/tests/integration/internal/tests/envd/signatures_test.go +++ b/tests/integration/internal/tests/envd/signatures_test.go @@ -30,10 +30,10 @@ func TestDownloadFileWhenAuthIsDisabled(t *testing.T) { filePath := "test.txt" textFile, contentType := utils.CreateTextFile(t, filePath, "Hello, World!") - writeRes, err := envdClient.HTTPClient.PostFilesWithBodyWithResponse( + writeRes, err := envdClient.HTTPClient.UploadFileWithBodyWithResponse( ctx, - &envd.PostFilesParams{ - Path: &filePath, + &envd.UploadFileParams{ + Path: filePath, Username: sharedUtils.ToPtr("user"), }, contentType, @@ -44,9 +44,9 @@ func TestDownloadFileWhenAuthIsDisabled(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusOK, writeRes.StatusCode()) - getRes, err := envdClient.HTTPClient.GetFilesWithResponse( + getRes, err := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &filePath, Username: sharedUtils.ToPtr("user")}, + &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.JSON201.SandboxID), ) @@ -71,10 +71,10 @@ func TestDownloadFileWithoutSigningWhenAuthIsEnabled(t *testing.T) { textFile, contentType := utils.CreateTextFile(t, filePath, "Hello, World!") writeFileSigning := generateSignature(filePath, "user", "write", nil, *envdToken) - writeRes, err := envdClient.HTTPClient.PostFilesWithBodyWithResponse( + writeRes, err := envdClient.HTTPClient.UploadFileWithBodyWithResponse( ctx, - &envd.PostFilesParams{ - Path: &filePath, + &envd.UploadFileParams{ + Path: filePath, Username: sharedUtils.ToPtr("user"), Signature: &writeFileSigning, }, @@ -89,7 +89,7 @@ func TestDownloadFileWithoutSigningWhenAuthIsEnabled(t *testing.T) { readRes, readErr := envdClient.HTTPClient.GetFiles( ctx, - &envd.GetFilesParams{Path: &filePath, Username: sharedUtils.ToPtr("user")}, + &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.JSON201.SandboxID), ) require.NoError(t, readErr) @@ -115,10 +115,10 @@ func TestDownloadFileWithSigningWhenAuthIsEnabled(t *testing.T) { writeFileSigning := generateSignature(filePath, "user", "write", nil, *envdToken) textFile, contentType := utils.CreateTextFile(t, filePath, "Hello, World!") - writeRes, err := envdClient.HTTPClient.PostFilesWithBodyWithResponse( + writeRes, err := envdClient.HTTPClient.UploadFileWithBodyWithResponse( ctx, - &envd.PostFilesParams{ - Path: &filePath, + &envd.UploadFileParams{ + Path: filePath, Username: sharedUtils.ToPtr("user"), Signature: &writeFileSigning, }, @@ -130,9 +130,9 @@ func TestDownloadFileWithSigningWhenAuthIsEnabled(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusOK, writeRes.StatusCode()) - readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( + readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{Path: &filePath, Username: sharedUtils.ToPtr("user"), Signature: &readFileSigning}, + &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user"), Signature: &readFileSigning}, setup.WithSandbox(t, sbx.JSON201.SandboxID), ) @@ -159,10 +159,10 @@ func TestDownloadWithAlreadyExpiredToken(t *testing.T) { signatureForRead := generateSignature(filePath, "user", "read", &signatureExpiration, *envdToken) readExpiration := int(signatureExpiration) - readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( + readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{ - Path: &filePath, + &envd.DownloadFileParams{ + Path: filePath, Username: sharedUtils.ToPtr("user"), Signature: &signatureForRead, SignatureExpiration: &readExpiration, @@ -193,10 +193,10 @@ func TestDownloadWithHealthyToken(t *testing.T) { signatureForRead := generateSignature(filePath, "user", "read", &signatureExpiration, *envdToken) readExpiration := int(signatureExpiration) - readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( + readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{ - Path: &filePath, + &envd.DownloadFileParams{ + Path: filePath, Username: sharedUtils.ToPtr("user"), Signature: &signatureForRead, SignatureExpiration: &readExpiration, @@ -227,10 +227,10 @@ func TestAccessWithNotCorrespondingSignatureAndSignatureExpiration(t *testing.T) signatureForRead := generateSignature(filePath, "user", "read", nil, *envdToken) readExpiration := int(signatureExpiration) - readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( + readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( ctx, - &envd.GetFilesParams{ - Path: &filePath, + &envd.DownloadFileParams{ + Path: filePath, Username: sharedUtils.ToPtr("user"), Signature: &signatureForRead, SignatureExpiration: &readExpiration, diff --git a/tests/integration/internal/utils/filesystem.go b/tests/integration/internal/utils/filesystem.go index 012f414618..5f37eac0b4 100644 --- a/tests/integration/internal/utils/filesystem.go +++ b/tests/integration/internal/utils/filesystem.go @@ -27,9 +27,9 @@ func UploadFile(tb testing.TB, ctx context.Context, sbx *api.Sandbox, envdClient reqEditors = append(reqEditors, setup.WithEnvdAccessToken(tb, *(sbx.EnvdAccessToken))) } - writeRes, err := envdClient.HTTPClient.PostFilesWithBodyWithResponse( + writeRes, err := envdClient.HTTPClient.UploadFileWithBodyWithResponse( ctx, - &envd.PostFilesParams{Path: &path, Username: utils.ToPtr("user")}, + &envd.UploadFileParams{Path: path, Username: utils.ToPtr("user")}, contentType, buffer, reqEditors..., From a9f817d1b04622f6955db07d0ef26c017058d07c Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Thu, 19 Feb 2026 17:21:55 -0800 Subject: [PATCH 05/11] fix: remove unnecessary type conversions and fix remaining renames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unnecessary string()/FilePath() conversions flagged by linter (FilePath is a type alias, not a distinct type) - Rename PostInitJSONRequestBody → InitSandboxJSONRequestBody in auth_test.go - Rename PostInitWithResponse → InitSandboxWithResponse in auth_test.go - Rename GetFiles → DownloadFile in signatures_test.go --- packages/envd/internal/api/download.go | 2 +- packages/envd/internal/api/download_test.go | 14 +++++++------- packages/envd/internal/api/upload.go | 4 ++-- tests/integration/internal/tests/envd/auth_test.go | 10 +++++----- .../internal/tests/envd/signatures_test.go | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/envd/internal/api/download.go b/packages/envd/internal/api/download.go index 3e643b17cf..7b3a8df7a2 100644 --- a/packages/envd/internal/api/download.go +++ b/packages/envd/internal/api/download.go @@ -22,7 +22,7 @@ func (a *API) DownloadFile(w http.ResponseWriter, r *http.Request, params Downlo var errorCode int var errMsg error - path := string(params.Path) + path := params.Path operationID := logs.AssignOperationID() diff --git a/packages/envd/internal/api/download_test.go b/packages/envd/internal/api/download_test.go index 9caec483f1..3ac47045b6 100644 --- a/packages/envd/internal/api/download_test.go +++ b/packages/envd/internal/api/download_test.go @@ -103,7 +103,7 @@ func TestGetFilesContentDisposition(t *testing.T) { // Call the handler params := DownloadFileParams{ - Path: FilePath(tempFile), + Path: tempFile, Username: ¤tUser.Username, } api.DownloadFile(w, req, params) @@ -152,7 +152,7 @@ func TestGetFilesContentDispositionWithNestedPath(t *testing.T) { // Call the handler params := DownloadFileParams{ - Path: FilePath(tempFile), + Path: tempFile, Username: ¤tUser.Username, } api.DownloadFile(w, req, params) @@ -197,7 +197,7 @@ func TestGetFiles_GzipEncoding_ExplicitIdentityOffWithRange(t *testing.T) { // Call the handler params := DownloadFileParams{ - Path: FilePath(tempFile), + Path: tempFile, Username: ¤tUser.Username, } api.DownloadFile(w, req, params) @@ -235,7 +235,7 @@ func TestGetFiles_GzipDownload(t *testing.T) { w := httptest.NewRecorder() params := DownloadFileParams{ - Path: FilePath(tempFile), + Path: tempFile, Username: ¤tUser.Username, } api.DownloadFile(w, req, params) @@ -301,7 +301,7 @@ func TestPostFiles_GzipUpload(t *testing.T) { w := httptest.NewRecorder() params := UploadFileParams{ - Path: FilePath(destPath), + Path: destPath, Username: ¤tUser.Username, } api.UploadFile(w, req, params) @@ -361,7 +361,7 @@ func TestGzipUploadThenGzipDownload(t *testing.T) { uploadW := httptest.NewRecorder() uploadParams := UploadFileParams{ - Path: FilePath(destPath), + Path: destPath, Username: ¤tUser.Username, } api.UploadFile(uploadW, uploadReq, uploadParams) @@ -378,7 +378,7 @@ func TestGzipUploadThenGzipDownload(t *testing.T) { downloadW := httptest.NewRecorder() downloadParams := DownloadFileParams{ - Path: FilePath(destPath), + Path: destPath, Username: ¤tUser.Username, } api.DownloadFile(downloadW, downloadReq, downloadParams) diff --git a/packages/envd/internal/api/upload.go b/packages/envd/internal/api/upload.go index 621daa14c9..1626b50883 100644 --- a/packages/envd/internal/api/upload.go +++ b/packages/envd/internal/api/upload.go @@ -111,7 +111,7 @@ func resolvePath(part *multipart.Part, paths *UploadSuccess, u *user.User, defau var pathToResolve string if params.Path != "" { - pathToResolve = string(params.Path) + pathToResolve = params.Path } else { var err error customPart := utils.NewCustomPart(part) @@ -186,7 +186,7 @@ func (a *API) UploadFile(w http.ResponseWriter, r *http.Request, params UploadFi var errorCode int var errMsg error - path := string(params.Path) + path := params.Path operationID := logs.AssignOperationID() diff --git a/tests/integration/internal/tests/envd/auth_test.go b/tests/integration/internal/tests/envd/auth_test.go index 1f04aaae44..96be5ecd03 100644 --- a/tests/integration/internal/tests/envd/auth_test.go +++ b/tests/integration/internal/tests/envd/auth_test.go @@ -89,7 +89,7 @@ func TestInitWithNilTokenOnSecuredSandboxReturnsUnauthorized(t *testing.T) { sandboxEnvdInitCall(t, ctx, envdInitCall{ sbx: sbx, client: envdClient, - body: envd.PostInitJSONRequestBody{}, + body: envd.InitSandboxJSONRequestBody{}, expectedResErr: nil, expectedResHttpStatus: http.StatusUnauthorized, }) @@ -111,7 +111,7 @@ func TestInitWithWrongTokenOnSecuredSandboxReturnsUnauthorized(t *testing.T) { sandboxEnvdInitCall(t, ctx, envdInitCall{ sbx: sbx, client: envdClient, - body: envd.PostInitJSONRequestBody{AccessToken: &wrongToken}, + body: envd.InitSandboxJSONRequestBody{AccessToken: &wrongToken}, expectedResErr: nil, expectedResHttpStatus: http.StatusUnauthorized, }) @@ -136,7 +136,7 @@ func TestChangeAccessAuthorizedToken(t *testing.T) { sbx: sbx, client: envdClient, authToken: envdAuthTokenA, // this is the old token used currently by envd - body: envd.PostInitJSONRequestBody{AccessToken: &envdAuthTokenB}, + body: envd.InitSandboxJSONRequestBody{AccessToken: &envdAuthTokenB}, expectedResErr: nil, expectedResHttpStatus: http.StatusUnauthorized, }) @@ -257,7 +257,7 @@ func TestAccessAuthorizedPathWithResumedSandboxWithoutAccessToken(t *testing.T) type envdInitCall struct { sbx *api.PostSandboxesResponse client *setup.EnvdClient - body envd.PostInitJSONRequestBody + body envd.InitSandboxJSONRequestBody authToken *string expectedResErr *error expectedResHttpStatus int @@ -271,7 +271,7 @@ func sandboxEnvdInitCall(t *testing.T, ctx context.Context, req envdInitCall) { envdReqSetup = append(envdReqSetup, setup.WithEnvdAccessToken(t, *req.authToken)) } - res, err := req.client.HTTPClient.PostInitWithResponse(ctx, req.body, envdReqSetup...) + res, err := req.client.HTTPClient.InitSandboxWithResponse(ctx, req.body, envdReqSetup...) if req.expectedResErr != nil { assert.Equal(t, *req.expectedResErr, err) } else { diff --git a/tests/integration/internal/tests/envd/signatures_test.go b/tests/integration/internal/tests/envd/signatures_test.go index 64469ec11e..c79ae8a5ce 100644 --- a/tests/integration/internal/tests/envd/signatures_test.go +++ b/tests/integration/internal/tests/envd/signatures_test.go @@ -87,7 +87,7 @@ func TestDownloadFileWithoutSigningWhenAuthIsEnabled(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusOK, writeRes.StatusCode()) - readRes, readErr := envdClient.HTTPClient.GetFiles( + readRes, readErr := envdClient.HTTPClient.DownloadFile( ctx, &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.JSON201.SandboxID), From f0533d1b93981f5ee46c35c178c50d514b14aac8 Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Thu, 19 Feb 2026 17:39:16 -0800 Subject: [PATCH 06/11] fix: revert Go code changes, move operationIds to post-processing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert all Go code and generated code changes to avoid breaking the build. The envd.yaml spec now only contains documentation-safe changes (descriptions, examples, 406 response, mem MiB fields) that don't affect oapi-codegen output. OperationIds and the AccessTokenAuth → SandboxAccessTokenAuth rename are now applied in generate_openapi.py during post-processing, so they appear in the public docs without changing generated Go type names. --- packages/envd/internal/api/api.gen.go | 146 ++++------ packages/envd/internal/api/download.go | 7 +- packages/envd/internal/api/download_test.go | 42 +-- packages/envd/internal/api/envs.go | 2 +- packages/envd/internal/api/init.go | 6 +- packages/envd/internal/api/init_test.go | 14 +- packages/envd/internal/api/upload.go | 15 +- packages/envd/spec/envd.yaml | 20 +- .../orchestrator/internal/sandbox/envd.go | 2 +- .../internal/sandbox/envd/envd.gen.go | 62 ++-- scripts/generate_openapi.py | 61 ++++ tests/integration/internal/envd/generated.go | 274 +++++++++--------- .../api/sandboxes/sandbox_auto_pause_test.go | 12 +- .../tests/api/templates/template_tags_test.go | 8 +- .../internal/tests/api/volumes/crud_test.go | 12 +- .../internal/tests/envd/auth_test.go | 18 +- .../internal/tests/envd/hyperloop_test.go | 12 +- .../internal/tests/envd/signatures_test.go | 48 +-- .../integration/internal/utils/filesystem.go | 4 +- 19 files changed, 388 insertions(+), 377 deletions(-) diff --git a/packages/envd/internal/api/api.gen.go b/packages/envd/internal/api/api.gen.go index 2c33384a4c..512747b05f 100644 --- a/packages/envd/internal/api/api.gen.go +++ b/packages/envd/internal/api/api.gen.go @@ -15,7 +15,7 @@ import ( ) const ( - SandboxAccessTokenAuthScopes = "SandboxAccessTokenAuth.Scopes" + AccessTokenAuthScopes = "AccessTokenAuth.Scopes" ) // Defines values for EntryInfoType. @@ -67,26 +67,17 @@ type Metrics struct { // MemTotal Total virtual memory in bytes MemTotal *int `json:"mem_total,omitempty"` - // MemTotalMib Total virtual memory in MiB - MemTotalMib *int `json:"mem_total_mib,omitempty"` - // MemUsed Used virtual memory in bytes MemUsed *int `json:"mem_used,omitempty"` - // MemUsedMib Used virtual memory in MiB - MemUsedMib *int `json:"mem_used_mib,omitempty"` - // Ts Unix timestamp in UTC for current sandbox time Ts *int64 `json:"ts,omitempty"` } -// VolumeMount NFS volume mount configuration +// VolumeMount Volume type VolumeMount struct { - // NfsTarget NFS server target address NfsTarget string `json:"nfs_target"` - - // Path Mount path inside the sandbox - Path string `json:"path"` + Path string `json:"path"` } // FilePath defines model for FilePath. @@ -113,52 +104,49 @@ type InvalidPath = Error // InvalidUser defines model for InvalidUser. type InvalidUser = Error -// NotAcceptable defines model for NotAcceptable. -type NotAcceptable = Error - // NotEnoughDiskSpace defines model for NotEnoughDiskSpace. type NotEnoughDiskSpace = Error // UploadSuccess defines model for UploadSuccess. type UploadSuccess = []EntryInfo -// DownloadFileParams defines parameters for DownloadFile. -type DownloadFileParams struct { - // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). - Path FilePath `form:"path" json:"path"` +// GetFilesParams defines parameters for GetFiles. +type GetFilesParams struct { + // Path Path to the file, URL encoded. Can be relative to user's home directory. + Path *FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. + // Username User used for setting the owner, or resolving relative paths. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". + // Signature Signature used for file access permission verification. Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. + // SignatureExpiration Signature expiration used for defining the expiration time of the signature. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// UploadFileMultipartBody defines parameters for UploadFile. -type UploadFileMultipartBody struct { +// PostFilesMultipartBody defines parameters for PostFiles. +type PostFilesMultipartBody struct { File *openapi_types.File `json:"file,omitempty"` } -// UploadFileParams defines parameters for UploadFile. -type UploadFileParams struct { - // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). - Path FilePath `form:"path" json:"path"` +// PostFilesParams defines parameters for PostFiles. +type PostFilesParams struct { + // Path Path to the file, URL encoded. Can be relative to user's home directory. + Path *FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. + // Username User used for setting the owner, or resolving relative paths. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". + // Signature Signature used for file access permission verification. Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. + // SignatureExpiration Signature expiration used for defining the expiration time of the signature. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// InitSandboxJSONBody defines parameters for InitSandbox. -type InitSandboxJSONBody struct { +// PostInitJSONBody defines parameters for PostInit. +type PostInitJSONBody struct { // AccessToken Access token for secure access to envd service AccessToken *SecureToken `json:"accessToken,omitempty"` @@ -179,29 +167,29 @@ type InitSandboxJSONBody struct { VolumeMounts *[]VolumeMount `json:"volumeMounts,omitempty"` } -// UploadFileMultipartRequestBody defines body for UploadFile for multipart/form-data ContentType. -type UploadFileMultipartRequestBody UploadFileMultipartBody +// PostFilesMultipartRequestBody defines body for PostFiles for multipart/form-data ContentType. +type PostFilesMultipartRequestBody PostFilesMultipartBody -// InitSandboxJSONRequestBody defines body for InitSandbox for application/json ContentType. -type InitSandboxJSONRequestBody InitSandboxJSONBody +// PostInitJSONRequestBody defines body for PostInit for application/json ContentType. +type PostInitJSONRequestBody PostInitJSONBody // ServerInterface represents all server handlers. type ServerInterface interface { // Get the environment variables // (GET /envs) - GetEnvVars(w http.ResponseWriter, r *http.Request) + GetEnvs(w http.ResponseWriter, r *http.Request) // Download a file // (GET /files) - DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) + GetFiles(w http.ResponseWriter, r *http.Request, params GetFilesParams) // Upload a file and ensure the parent directories exist. If the file exists, it will be overwritten. // (POST /files) - UploadFile(w http.ResponseWriter, r *http.Request, params UploadFileParams) + PostFiles(w http.ResponseWriter, r *http.Request, params PostFilesParams) // Check the health of the service // (GET /health) GetHealth(w http.ResponseWriter, r *http.Request) // Set initial vars, ensure the time and metadata is synced with the host // (POST /init) - InitSandbox(w http.ResponseWriter, r *http.Request) + PostInit(w http.ResponseWriter, r *http.Request) // Get the stats of the service // (GET /metrics) GetMetrics(w http.ResponseWriter, r *http.Request) @@ -213,19 +201,19 @@ type Unimplemented struct{} // Get the environment variables // (GET /envs) -func (_ Unimplemented) GetEnvVars(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) GetEnvs(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } // Download a file // (GET /files) -func (_ Unimplemented) DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) { +func (_ Unimplemented) GetFiles(w http.ResponseWriter, r *http.Request, params GetFilesParams) { w.WriteHeader(http.StatusNotImplemented) } // Upload a file and ensure the parent directories exist. If the file exists, it will be overwritten. // (POST /files) -func (_ Unimplemented) UploadFile(w http.ResponseWriter, r *http.Request, params UploadFileParams) { +func (_ Unimplemented) PostFiles(w http.ResponseWriter, r *http.Request, params PostFilesParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -237,7 +225,7 @@ func (_ Unimplemented) GetHealth(w http.ResponseWriter, r *http.Request) { // Set initial vars, ensure the time and metadata is synced with the host // (POST /init) -func (_ Unimplemented) InitSandbox(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) PostInit(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -256,17 +244,17 @@ type ServerInterfaceWrapper struct { type MiddlewareFunc func(http.Handler) http.Handler -// GetEnvVars operation middleware -func (siw *ServerInterfaceWrapper) GetEnvVars(w http.ResponseWriter, r *http.Request) { +// GetEnvs operation middleware +func (siw *ServerInterfaceWrapper) GetEnvs(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ctx = context.WithValue(ctx, SandboxAccessTokenAuthScopes, []string{}) + ctx = context.WithValue(ctx, AccessTokenAuthScopes, []string{}) r = r.WithContext(ctx) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetEnvVars(w, r) + siw.Handler.GetEnvs(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -276,30 +264,23 @@ func (siw *ServerInterfaceWrapper) GetEnvVars(w http.ResponseWriter, r *http.Req handler.ServeHTTP(w, r) } -// DownloadFile operation middleware -func (siw *ServerInterfaceWrapper) DownloadFile(w http.ResponseWriter, r *http.Request) { +// GetFiles operation middleware +func (siw *ServerInterfaceWrapper) GetFiles(w http.ResponseWriter, r *http.Request) { var err error ctx := r.Context() - ctx = context.WithValue(ctx, SandboxAccessTokenAuthScopes, []string{}) + ctx = context.WithValue(ctx, AccessTokenAuthScopes, []string{}) r = r.WithContext(ctx) // Parameter object where we will unmarshal all parameters from the context - var params DownloadFileParams - - // ------------- Required query parameter "path" ------------- + var params GetFilesParams - if paramValue := r.URL.Query().Get("path"); paramValue != "" { + // ------------- Optional query parameter "path" ------------- - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) return @@ -330,7 +311,7 @@ func (siw *ServerInterfaceWrapper) DownloadFile(w http.ResponseWriter, r *http.R } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DownloadFile(w, r, params) + siw.Handler.GetFiles(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -340,30 +321,23 @@ func (siw *ServerInterfaceWrapper) DownloadFile(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r) } -// UploadFile operation middleware -func (siw *ServerInterfaceWrapper) UploadFile(w http.ResponseWriter, r *http.Request) { +// PostFiles operation middleware +func (siw *ServerInterfaceWrapper) PostFiles(w http.ResponseWriter, r *http.Request) { var err error ctx := r.Context() - ctx = context.WithValue(ctx, SandboxAccessTokenAuthScopes, []string{}) + ctx = context.WithValue(ctx, AccessTokenAuthScopes, []string{}) r = r.WithContext(ctx) // Parameter object where we will unmarshal all parameters from the context - var params UploadFileParams - - // ------------- Required query parameter "path" ------------- + var params PostFilesParams - if paramValue := r.URL.Query().Get("path"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) - return - } + // ------------- Optional query parameter "path" ------------- - err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + err = runtime.BindQueryParameter("form", true, false, "path", r.URL.Query(), ¶ms.Path) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) return @@ -394,7 +368,7 @@ func (siw *ServerInterfaceWrapper) UploadFile(w http.ResponseWriter, r *http.Req } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UploadFile(w, r, params) + siw.Handler.PostFiles(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -418,17 +392,17 @@ func (siw *ServerInterfaceWrapper) GetHealth(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r) } -// InitSandbox operation middleware -func (siw *ServerInterfaceWrapper) InitSandbox(w http.ResponseWriter, r *http.Request) { +// PostInit operation middleware +func (siw *ServerInterfaceWrapper) PostInit(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ctx = context.WithValue(ctx, SandboxAccessTokenAuthScopes, []string{}) + ctx = context.WithValue(ctx, AccessTokenAuthScopes, []string{}) r = r.WithContext(ctx) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.InitSandbox(w, r) + siw.Handler.PostInit(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -443,7 +417,7 @@ func (siw *ServerInterfaceWrapper) GetMetrics(w http.ResponseWriter, r *http.Req ctx := r.Context() - ctx = context.WithValue(ctx, SandboxAccessTokenAuthScopes, []string{}) + ctx = context.WithValue(ctx, AccessTokenAuthScopes, []string{}) r = r.WithContext(ctx) @@ -572,19 +546,19 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl } r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/envs", wrapper.GetEnvVars) + r.Get(options.BaseURL+"/envs", wrapper.GetEnvs) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/files", wrapper.DownloadFile) + r.Get(options.BaseURL+"/files", wrapper.GetFiles) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/files", wrapper.UploadFile) + r.Post(options.BaseURL+"/files", wrapper.PostFiles) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/health", wrapper.GetHealth) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/init", wrapper.InitSandbox) + r.Post(options.BaseURL+"/init", wrapper.PostInit) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/metrics", wrapper.GetMetrics) diff --git a/packages/envd/internal/api/download.go b/packages/envd/internal/api/download.go index 7b3a8df7a2..8b965bd817 100644 --- a/packages/envd/internal/api/download.go +++ b/packages/envd/internal/api/download.go @@ -16,13 +16,16 @@ import ( "github.com/e2b-dev/infra/packages/envd/internal/permissions" ) -func (a *API) DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) { +func (a *API) GetFiles(w http.ResponseWriter, r *http.Request, params GetFilesParams) { defer r.Body.Close() var errorCode int var errMsg error - path := params.Path + var path string + if params.Path != nil { + path = *params.Path + } operationID := logs.AssignOperationID() diff --git a/packages/envd/internal/api/download_test.go b/packages/envd/internal/api/download_test.go index 3ac47045b6..fabcb55a57 100644 --- a/packages/envd/internal/api/download_test.go +++ b/packages/envd/internal/api/download_test.go @@ -102,11 +102,11 @@ func TestGetFilesContentDisposition(t *testing.T) { w := httptest.NewRecorder() // Call the handler - params := DownloadFileParams{ - Path: tempFile, + params := GetFilesParams{ + Path: &tempFile, Username: ¤tUser.Username, } - api.DownloadFile(w, req, params) + api.GetFiles(w, req, params) // Check response resp := w.Result() @@ -151,11 +151,11 @@ func TestGetFilesContentDispositionWithNestedPath(t *testing.T) { w := httptest.NewRecorder() // Call the handler - params := DownloadFileParams{ - Path: tempFile, + params := GetFilesParams{ + Path: &tempFile, Username: ¤tUser.Username, } - api.DownloadFile(w, req, params) + api.GetFiles(w, req, params) // Check response resp := w.Result() @@ -196,11 +196,11 @@ func TestGetFiles_GzipEncoding_ExplicitIdentityOffWithRange(t *testing.T) { w := httptest.NewRecorder() // Call the handler - params := DownloadFileParams{ - Path: tempFile, + params := GetFilesParams{ + Path: &tempFile, Username: ¤tUser.Username, } - api.DownloadFile(w, req, params) + api.GetFiles(w, req, params) // Check response resp := w.Result() @@ -234,11 +234,11 @@ func TestGetFiles_GzipDownload(t *testing.T) { req.Header.Set("Accept-Encoding", "gzip") w := httptest.NewRecorder() - params := DownloadFileParams{ - Path: tempFile, + params := GetFilesParams{ + Path: &tempFile, Username: ¤tUser.Username, } - api.DownloadFile(w, req, params) + api.GetFiles(w, req, params) resp := w.Result() defer resp.Body.Close() @@ -300,11 +300,11 @@ func TestPostFiles_GzipUpload(t *testing.T) { req.Header.Set("Content-Encoding", "gzip") w := httptest.NewRecorder() - params := UploadFileParams{ - Path: destPath, + params := PostFilesParams{ + Path: &destPath, Username: ¤tUser.Username, } - api.UploadFile(w, req, params) + api.PostFiles(w, req, params) resp := w.Result() defer resp.Body.Close() @@ -360,11 +360,11 @@ func TestGzipUploadThenGzipDownload(t *testing.T) { uploadReq.Header.Set("Content-Encoding", "gzip") uploadW := httptest.NewRecorder() - uploadParams := UploadFileParams{ - Path: destPath, + uploadParams := PostFilesParams{ + Path: &destPath, Username: ¤tUser.Username, } - api.UploadFile(uploadW, uploadReq, uploadParams) + api.PostFiles(uploadW, uploadReq, uploadParams) uploadResp := uploadW.Result() defer uploadResp.Body.Close() @@ -377,11 +377,11 @@ func TestGzipUploadThenGzipDownload(t *testing.T) { downloadReq.Header.Set("Accept-Encoding", "gzip") downloadW := httptest.NewRecorder() - downloadParams := DownloadFileParams{ - Path: destPath, + downloadParams := GetFilesParams{ + Path: &destPath, Username: ¤tUser.Username, } - api.DownloadFile(downloadW, downloadReq, downloadParams) + api.GetFiles(downloadW, downloadReq, downloadParams) downloadResp := downloadW.Result() defer downloadResp.Body.Close() diff --git a/packages/envd/internal/api/envs.go b/packages/envd/internal/api/envs.go index e3b3686749..28d34da3aa 100644 --- a/packages/envd/internal/api/envs.go +++ b/packages/envd/internal/api/envs.go @@ -7,7 +7,7 @@ import ( "github.com/e2b-dev/infra/packages/envd/internal/logs" ) -func (a *API) GetEnvVars(w http.ResponseWriter, _ *http.Request) { +func (a *API) GetEnvs(w http.ResponseWriter, _ *http.Request) { operationID := logs.AssignOperationID() a.logger.Debug().Str(string(logs.OperationIDKey), operationID).Msg("Getting env vars") diff --git a/packages/envd/internal/api/init.go b/packages/envd/internal/api/init.go index 41d3b1ef3d..29a1e7e79e 100644 --- a/packages/envd/internal/api/init.go +++ b/packages/envd/internal/api/init.go @@ -91,7 +91,7 @@ func (a *API) checkMMDSHash(ctx context.Context, requestToken *SecureToken) (boo return keys.HashAccessTokenBytes(tokenBytes) == mmdsHash, true } -func (a *API) InitSandbox(w http.ResponseWriter, r *http.Request) { +func (a *API) PostInit(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() ctx := r.Context() @@ -111,7 +111,7 @@ func (a *API) InitSandbox(w http.ResponseWriter, r *http.Request) { return } - var initRequest InitSandboxJSONBody + var initRequest PostInitJSONBody if len(body) > 0 { err = json.Unmarshal(body, &initRequest) if err != nil { @@ -160,7 +160,7 @@ func (a *API) InitSandbox(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } -func (a *API) SetData(ctx context.Context, logger zerolog.Logger, data InitSandboxJSONBody) error { +func (a *API) SetData(ctx context.Context, logger zerolog.Logger, data PostInitJSONBody) error { // Validate access token before proceeding with any action // The request must provide a token that is either: // 1. Matches the existing access token (if set), OR diff --git a/packages/envd/internal/api/init_test.go b/packages/envd/internal/api/init_test.go index a35763a76e..9877104e09 100644 --- a/packages/envd/internal/api/init_test.go +++ b/packages/envd/internal/api/init_test.go @@ -453,7 +453,7 @@ func TestSetData(t *testing.T) { mmdsClient := &mockMMDSClient{hash: tt.mmdsHash, err: tt.mmdsErr} api := newTestAPI(tt.existingToken, mmdsClient) - data := InitSandboxJSONBody{ + data := PostInitJSONBody{ AccessToken: tt.requestToken, } @@ -481,7 +481,7 @@ func TestSetData(t *testing.T) { api := newTestAPI(nil, mmdsClient) envVars := EnvVars{"FOO": "bar", "BAZ": "qux"} - data := InitSandboxJSONBody{ + data := PostInitJSONBody{ EnvVars: &envVars, } @@ -501,7 +501,7 @@ func TestSetData(t *testing.T) { mmdsClient := &mockMMDSClient{hash: "", err: assert.AnError} api := newTestAPI(nil, mmdsClient) - data := InitSandboxJSONBody{ + data := PostInitJSONBody{ DefaultUser: utilsShared.ToPtr("testuser"), } @@ -517,7 +517,7 @@ func TestSetData(t *testing.T) { api := newTestAPI(nil, mmdsClient) api.defaults.User = "original" - data := InitSandboxJSONBody{ + data := PostInitJSONBody{ DefaultUser: utilsShared.ToPtr(""), } @@ -532,7 +532,7 @@ func TestSetData(t *testing.T) { mmdsClient := &mockMMDSClient{hash: "", err: assert.AnError} api := newTestAPI(nil, mmdsClient) - data := InitSandboxJSONBody{ + data := PostInitJSONBody{ DefaultWorkdir: utilsShared.ToPtr("/home/user"), } @@ -550,7 +550,7 @@ func TestSetData(t *testing.T) { originalWorkdir := "/original" api.defaults.Workdir = &originalWorkdir - data := InitSandboxJSONBody{ + data := PostInitJSONBody{ DefaultWorkdir: utilsShared.ToPtr(""), } @@ -567,7 +567,7 @@ func TestSetData(t *testing.T) { api := newTestAPI(nil, mmdsClient) envVars := EnvVars{"KEY": "value"} - data := InitSandboxJSONBody{ + data := PostInitJSONBody{ AccessToken: secureTokenPtr("token"), DefaultUser: utilsShared.ToPtr("user"), DefaultWorkdir: utilsShared.ToPtr("/workdir"), diff --git a/packages/envd/internal/api/upload.go b/packages/envd/internal/api/upload.go index 1626b50883..fb6868831d 100644 --- a/packages/envd/internal/api/upload.go +++ b/packages/envd/internal/api/upload.go @@ -107,11 +107,11 @@ func processFile(r *http.Request, path string, part io.Reader, uid, gid int, log return http.StatusNoContent, nil } -func resolvePath(part *multipart.Part, paths *UploadSuccess, u *user.User, defaultPath *string, params UploadFileParams) (string, error) { +func resolvePath(part *multipart.Part, paths *UploadSuccess, u *user.User, defaultPath *string, params PostFilesParams) (string, error) { var pathToResolve string - if params.Path != "" { - pathToResolve = params.Path + if params.Path != nil { + pathToResolve = *params.Path } else { var err error customPart := utils.NewCustomPart(part) @@ -148,7 +148,7 @@ func resolvePath(part *multipart.Part, paths *UploadSuccess, u *user.User, defau return filePath, nil } -func (a *API) handlePart(r *http.Request, part *multipart.Part, paths UploadSuccess, u *user.User, uid, gid int, operationID string, params UploadFileParams) (*EntryInfo, int, error) { +func (a *API) handlePart(r *http.Request, part *multipart.Part, paths UploadSuccess, u *user.User, uid, gid int, operationID string, params PostFilesParams) (*EntryInfo, int, error) { defer part.Close() if part.FormName() != "file" { @@ -178,7 +178,7 @@ func (a *API) handlePart(r *http.Request, part *multipart.Part, paths UploadSucc }, http.StatusOK, nil } -func (a *API) UploadFile(w http.ResponseWriter, r *http.Request, params UploadFileParams) { +func (a *API) PostFiles(w http.ResponseWriter, r *http.Request, params PostFilesParams) { // Capture original body to ensure it's always closed originalBody := r.Body defer originalBody.Close() @@ -186,7 +186,10 @@ func (a *API) UploadFile(w http.ResponseWriter, r *http.Request, params UploadFi var errorCode int var errMsg error - path := params.Path + var path string + if params.Path != nil { + path = *params.Path + } operationID := logs.AssignOperationID() diff --git a/packages/envd/spec/envd.yaml b/packages/envd/spec/envd.yaml index 0522c070ff..fb1997b18f 100644 --- a/packages/envd/spec/envd.yaml +++ b/packages/envd/spec/envd.yaml @@ -10,7 +10,6 @@ tags: paths: /health: get: - operationId: getHealth summary: Check the health of the service responses: "204": @@ -18,10 +17,9 @@ paths: /metrics: get: - operationId: getMetrics summary: Get the stats of the service security: - - SandboxAccessTokenAuth: [] + - AccessTokenAuth: [] - {} responses: "200": @@ -33,10 +31,9 @@ paths: /init: post: - operationId: initSandbox summary: Set initial vars, ensure the time and metadata is synced with the host security: - - SandboxAccessTokenAuth: [] + - AccessTokenAuth: [] - {} requestBody: content: @@ -73,10 +70,9 @@ paths: /envs: get: - operationId: getEnvVars summary: Get the environment variables security: - - SandboxAccessTokenAuth: [] + - AccessTokenAuth: [] - {} responses: "200": @@ -88,11 +84,10 @@ paths: /files: get: - operationId: downloadFile summary: Download a file tags: [files] security: - - SandboxAccessTokenAuth: [] + - AccessTokenAuth: [] - {} parameters: - $ref: "#/components/parameters/FilePath" @@ -113,11 +108,10 @@ paths: "500": $ref: "#/components/responses/InternalServerError" post: - operationId: uploadFile summary: Upload a file and ensure the parent directories exist. If the file exists, it will be overwritten. tags: [files] security: - - SandboxAccessTokenAuth: [] + - AccessTokenAuth: [] - {} parameters: - $ref: "#/components/parameters/FilePath" @@ -140,7 +134,7 @@ paths: components: securitySchemes: - SandboxAccessTokenAuth: + AccessTokenAuth: type: apiKey in: header name: X-Access-Token @@ -149,7 +143,7 @@ components: FilePath: name: path in: query - required: true + required: false description: Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). schema: type: string diff --git a/packages/orchestrator/internal/sandbox/envd.go b/packages/orchestrator/internal/sandbox/envd.go index de5a30e099..4b5de9147a 100644 --- a/packages/orchestrator/internal/sandbox/envd.go +++ b/packages/orchestrator/internal/sandbox/envd.go @@ -35,7 +35,7 @@ func (s *Sandbox) doRequestWithInfiniteRetries( ) (*http.Response, int64, error) { requestCount := int64(0) - jsonBody := &envd.InitSandboxJSONBody{ + jsonBody := &envd.PostInitJSONBody{ EnvVars: s.Config.Envd.Vars, HyperloopIP: s.config.NetworkConfig.OrchestratorInSandboxIPAddress, AccessToken: utils.DerefOrDefault(s.Config.Envd.AccessToken, ""), diff --git a/packages/orchestrator/internal/sandbox/envd/envd.gen.go b/packages/orchestrator/internal/sandbox/envd/envd.gen.go index 905c14728b..73d03e999b 100644 --- a/packages/orchestrator/internal/sandbox/envd/envd.gen.go +++ b/packages/orchestrator/internal/sandbox/envd/envd.gen.go @@ -10,7 +10,7 @@ import ( ) const ( - SandboxAccessTokenAuthScopes = "SandboxAccessTokenAuth.Scopes" + AccessTokenAuthScopes = "AccessTokenAuth.Scopes" ) // Defines values for EntryInfoType. @@ -62,26 +62,17 @@ type Metrics struct { // MemTotal Total virtual memory in bytes MemTotal int `json:"mem_total,omitempty"` - // MemTotalMib Total virtual memory in MiB - MemTotalMib int `json:"mem_total_mib,omitempty"` - // MemUsed Used virtual memory in bytes MemUsed int `json:"mem_used,omitempty"` - // MemUsedMib Used virtual memory in MiB - MemUsedMib int `json:"mem_used_mib,omitempty"` - // Ts Unix timestamp in UTC for current sandbox time Ts int64 `json:"ts,omitempty"` } -// VolumeMount NFS volume mount configuration +// VolumeMount Volume type VolumeMount struct { - // NfsTarget NFS server target address NfsTarget string `json:"nfs_target"` - - // Path Mount path inside the sandbox - Path string `json:"path"` + Path string `json:"path"` } // FilePath defines model for FilePath. @@ -108,52 +99,49 @@ type InvalidPath = Error // InvalidUser defines model for InvalidUser. type InvalidUser = Error -// NotAcceptable defines model for NotAcceptable. -type NotAcceptable = Error - // NotEnoughDiskSpace defines model for NotEnoughDiskSpace. type NotEnoughDiskSpace = Error // UploadSuccess defines model for UploadSuccess. type UploadSuccess = []EntryInfo -// DownloadFileParams defines parameters for DownloadFile. -type DownloadFileParams struct { - // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). - Path FilePath `form:"path" json:"path"` +// GetFilesParams defines parameters for GetFiles. +type GetFilesParams struct { + // Path Path to the file, URL encoded. Can be relative to user's home directory. + Path FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. + // Username User used for setting the owner, or resolving relative paths. Username User `form:"username,omitempty" json:"username,omitempty"` - // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". + // Signature Signature used for file access permission verification. Signature Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. + // SignatureExpiration Signature expiration used for defining the expiration time of the signature. SignatureExpiration SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// UploadFileMultipartBody defines parameters for UploadFile. -type UploadFileMultipartBody struct { +// PostFilesMultipartBody defines parameters for PostFiles. +type PostFilesMultipartBody struct { File openapi_types.File `json:"file,omitempty"` } -// UploadFileParams defines parameters for UploadFile. -type UploadFileParams struct { - // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). - Path FilePath `form:"path" json:"path"` +// PostFilesParams defines parameters for PostFiles. +type PostFilesParams struct { + // Path Path to the file, URL encoded. Can be relative to user's home directory. + Path FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. + // Username User used for setting the owner, or resolving relative paths. Username User `form:"username,omitempty" json:"username,omitempty"` - // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". + // Signature Signature used for file access permission verification. Signature Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. + // SignatureExpiration Signature expiration used for defining the expiration time of the signature. SignatureExpiration SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// InitSandboxJSONBody defines parameters for InitSandbox. -type InitSandboxJSONBody struct { +// PostInitJSONBody defines parameters for PostInit. +type PostInitJSONBody struct { // AccessToken Access token for secure access to envd service AccessToken SecureToken `json:"accessToken,omitempty"` @@ -174,8 +162,8 @@ type InitSandboxJSONBody struct { VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` } -// UploadFileMultipartRequestBody defines body for UploadFile for multipart/form-data ContentType. -type UploadFileMultipartRequestBody UploadFileMultipartBody +// PostFilesMultipartRequestBody defines body for PostFiles for multipart/form-data ContentType. +type PostFilesMultipartRequestBody PostFilesMultipartBody -// InitSandboxJSONRequestBody defines body for InitSandbox for application/json ContentType. -type InitSandboxJSONRequestBody InitSandboxJSONBody +// PostInitJSONRequestBody defines body for PostInit for application/json ContentType. +type PostInitJSONRequestBody PostInitJSONBody diff --git a/scripts/generate_openapi.py b/scripts/generate_openapi.py index 52fbb242a3..4a32881c1b 100644 --- a/scripts/generate_openapi.py +++ b/scripts/generate_openapi.py @@ -418,6 +418,65 @@ def fix_security_schemes(spec: dict[str, Any]) -> None: scheme["in"] = scheme.pop("scheme") +def rename_envd_auth_scheme(spec: dict[str, Any]) -> None: + """Rename AccessTokenAuth → SandboxAccessTokenAuth in the merged spec. + + The source envd.yaml uses AccessTokenAuth for code generation compatibility, + but the public docs need SandboxAccessTokenAuth to avoid collisions with + the platform API's AccessTokenAuth scheme. + """ + old_name = "AccessTokenAuth" + new_name = SANDBOX_AUTH_SCHEME + schemes = spec.get("components", {}).get("securitySchemes", {}) + if old_name in schemes: + schemes[new_name] = schemes.pop(old_name) + # Update all security references in operations + for path_item in spec.get("paths", {}).values(): + for method in ("get", "post", "put", "patch", "delete", "head", "options"): + op = path_item.get(method) + if not op or "security" not in op: + continue + for sec_req in op["security"]: + if old_name in sec_req: + sec_req[new_name] = sec_req.pop(old_name) + # Update top-level security + for sec_req in spec.get("security", []): + if old_name in sec_req: + sec_req[new_name] = sec_req.pop(old_name) + + +# Mapping of (path, method) to desired operationId for the public docs. +# These are added at post-processing time to avoid breaking Go code generation +# (oapi-codegen derives type names from operationIds). +ENVD_OPERATION_IDS: dict[tuple[str, str], str] = { + ("/health", "get"): "getHealth", + ("/metrics", "get"): "getMetrics", + ("/init", "post"): "initSandbox", + ("/envs", "get"): "getEnvVars", + ("/files", "get"): "downloadFile", + ("/files", "post"): "uploadFile", +} + + +def add_operation_ids(spec: dict[str, Any]) -> None: + """Add operationIds to envd endpoints for clean documentation. + + These are added at post-processing time (not in the source spec) to + avoid changing generated Go type names. + """ + count = 0 + for (path, method), op_id in ENVD_OPERATION_IDS.items(): + path_item = spec.get("paths", {}).get(path) + if not path_item: + continue + op = path_item.get(method) + if op and "operationId" not in op: + op["operationId"] = op_id + count += 1 + if count: + print(f"==> Added {count} operationIds to envd endpoints") + + def _strip_supabase_security(path_item: dict[str, Any]) -> None: """Remove Supabase security entries from all operations in a path item. @@ -663,6 +722,8 @@ def main() -> None: # Fix known issues fix_security_schemes(merged) + rename_envd_auth_scheme(merged) + add_operation_ids(merged) # Remove internal/unwanted paths filter_paths(merged) diff --git a/tests/integration/internal/envd/generated.go b/tests/integration/internal/envd/generated.go index c0933ce8a4..c0461908a3 100644 --- a/tests/integration/internal/envd/generated.go +++ b/tests/integration/internal/envd/generated.go @@ -19,7 +19,7 @@ import ( ) const ( - SandboxAccessTokenAuthScopes = "SandboxAccessTokenAuth.Scopes" + AccessTokenAuthScopes = "AccessTokenAuth.Scopes" ) // Defines values for EntryInfoType. @@ -71,26 +71,17 @@ type Metrics struct { // MemTotal Total virtual memory in bytes MemTotal *int `json:"mem_total,omitempty"` - // MemTotalMib Total virtual memory in MiB - MemTotalMib *int `json:"mem_total_mib,omitempty"` - // MemUsed Used virtual memory in bytes MemUsed *int `json:"mem_used,omitempty"` - // MemUsedMib Used virtual memory in MiB - MemUsedMib *int `json:"mem_used_mib,omitempty"` - // Ts Unix timestamp in UTC for current sandbox time Ts *int64 `json:"ts,omitempty"` } -// VolumeMount NFS volume mount configuration +// VolumeMount Volume type VolumeMount struct { - // NfsTarget NFS server target address NfsTarget string `json:"nfs_target"` - - // Path Mount path inside the sandbox - Path string `json:"path"` + Path string `json:"path"` } // FilePath defines model for FilePath. @@ -117,52 +108,49 @@ type InvalidPath = Error // InvalidUser defines model for InvalidUser. type InvalidUser = Error -// NotAcceptable defines model for NotAcceptable. -type NotAcceptable = Error - // NotEnoughDiskSpace defines model for NotEnoughDiskSpace. type NotEnoughDiskSpace = Error // UploadSuccess defines model for UploadSuccess. type UploadSuccess = []EntryInfo -// DownloadFileParams defines parameters for DownloadFile. -type DownloadFileParams struct { - // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). - Path FilePath `form:"path" json:"path"` +// GetFilesParams defines parameters for GetFiles. +type GetFilesParams struct { + // Path Path to the file, URL encoded. Can be relative to user's home directory. + Path *FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. + // Username User used for setting the owner, or resolving relative paths. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". + // Signature Signature used for file access permission verification. Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. + // SignatureExpiration Signature expiration used for defining the expiration time of the signature. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// UploadFileMultipartBody defines parameters for UploadFile. -type UploadFileMultipartBody struct { +// PostFilesMultipartBody defines parameters for PostFiles. +type PostFilesMultipartBody struct { File *openapi_types.File `json:"file,omitempty"` } -// UploadFileParams defines parameters for UploadFile. -type UploadFileParams struct { - // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). - Path FilePath `form:"path" json:"path"` +// PostFilesParams defines parameters for PostFiles. +type PostFilesParams struct { + // Path Path to the file, URL encoded. Can be relative to user's home directory. + Path *FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. + // Username User used for setting the owner, or resolving relative paths. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". + // Signature Signature used for file access permission verification. Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. + // SignatureExpiration Signature expiration used for defining the expiration time of the signature. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } -// InitSandboxJSONBody defines parameters for InitSandbox. -type InitSandboxJSONBody struct { +// PostInitJSONBody defines parameters for PostInit. +type PostInitJSONBody struct { // AccessToken Access token for secure access to envd service AccessToken *SecureToken `json:"accessToken,omitempty"` @@ -183,11 +171,11 @@ type InitSandboxJSONBody struct { VolumeMounts *[]VolumeMount `json:"volumeMounts,omitempty"` } -// UploadFileMultipartRequestBody defines body for UploadFile for multipart/form-data ContentType. -type UploadFileMultipartRequestBody UploadFileMultipartBody +// PostFilesMultipartRequestBody defines body for PostFiles for multipart/form-data ContentType. +type PostFilesMultipartRequestBody PostFilesMultipartBody -// InitSandboxJSONRequestBody defines body for InitSandbox for application/json ContentType. -type InitSandboxJSONRequestBody InitSandboxJSONBody +// PostInitJSONRequestBody defines body for PostInit for application/json ContentType. +type PostInitJSONRequestBody PostInitJSONBody // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -262,29 +250,29 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { - // GetEnvVars request - GetEnvVars(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetEnvs request + GetEnvs(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // DownloadFile request - DownloadFile(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetFiles request + GetFiles(ctx context.Context, params *GetFilesParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // UploadFileWithBody request with any body - UploadFileWithBody(ctx context.Context, params *UploadFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostFilesWithBody request with any body + PostFilesWithBody(ctx context.Context, params *PostFilesParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) // GetHealth request GetHealth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // InitSandboxWithBody request with any body - InitSandboxWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostInitWithBody request with any body + PostInitWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - InitSandbox(ctx context.Context, body InitSandboxJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostInit(ctx context.Context, body PostInitJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetMetrics request GetMetrics(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) } -func (c *Client) GetEnvVars(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetEnvVarsRequest(c.Server) +func (c *Client) GetEnvs(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetEnvsRequest(c.Server) if err != nil { return nil, err } @@ -295,8 +283,8 @@ func (c *Client) GetEnvVars(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) DownloadFile(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDownloadFileRequest(c.Server, params) +func (c *Client) GetFiles(ctx context.Context, params *GetFilesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetFilesRequest(c.Server, params) if err != nil { return nil, err } @@ -307,8 +295,8 @@ func (c *Client) DownloadFile(ctx context.Context, params *DownloadFileParams, r return c.Client.Do(req) } -func (c *Client) UploadFileWithBody(ctx context.Context, params *UploadFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUploadFileRequestWithBody(c.Server, params, contentType, body) +func (c *Client) PostFilesWithBody(ctx context.Context, params *PostFilesParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostFilesRequestWithBody(c.Server, params, contentType, body) if err != nil { return nil, err } @@ -331,8 +319,8 @@ func (c *Client) GetHealth(ctx context.Context, reqEditors ...RequestEditorFn) ( return c.Client.Do(req) } -func (c *Client) InitSandboxWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewInitSandboxRequestWithBody(c.Server, contentType, body) +func (c *Client) PostInitWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostInitRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -343,8 +331,8 @@ func (c *Client) InitSandboxWithBody(ctx context.Context, contentType string, bo return c.Client.Do(req) } -func (c *Client) InitSandbox(ctx context.Context, body InitSandboxJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewInitSandboxRequest(c.Server, body) +func (c *Client) PostInit(ctx context.Context, body PostInitJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostInitRequest(c.Server, body) if err != nil { return nil, err } @@ -367,8 +355,8 @@ func (c *Client) GetMetrics(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -// NewGetEnvVarsRequest generates requests for GetEnvVars -func NewGetEnvVarsRequest(server string) (*http.Request, error) { +// NewGetEnvsRequest generates requests for GetEnvs +func NewGetEnvsRequest(server string) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -394,8 +382,8 @@ func NewGetEnvVarsRequest(server string) (*http.Request, error) { return req, nil } -// NewDownloadFileRequest generates requests for DownloadFile -func NewDownloadFileRequest(server string, params *DownloadFileParams) (*http.Request, error) { +// NewGetFilesRequest generates requests for GetFiles +func NewGetFilesRequest(server string, params *GetFilesParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -416,16 +404,20 @@ func NewDownloadFileRequest(server string, params *DownloadFileParams) (*http.Re if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } if params.Username != nil { @@ -487,8 +479,8 @@ func NewDownloadFileRequest(server string, params *DownloadFileParams) (*http.Re return req, nil } -// NewUploadFileRequestWithBody generates requests for UploadFile with any type of body -func NewUploadFileRequestWithBody(server string, params *UploadFileParams, contentType string, body io.Reader) (*http.Request, error) { +// NewPostFilesRequestWithBody generates requests for PostFiles with any type of body +func NewPostFilesRequestWithBody(server string, params *PostFilesParams, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -509,16 +501,20 @@ func NewUploadFileRequestWithBody(server string, params *UploadFileParams, conte if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.Path != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, *params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } if params.Username != nil { @@ -609,19 +605,19 @@ func NewGetHealthRequest(server string) (*http.Request, error) { return req, nil } -// NewInitSandboxRequest calls the generic InitSandbox builder with application/json body -func NewInitSandboxRequest(server string, body InitSandboxJSONRequestBody) (*http.Request, error) { +// NewPostInitRequest calls the generic PostInit builder with application/json body +func NewPostInitRequest(server string, body PostInitJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewInitSandboxRequestWithBody(server, "application/json", bodyReader) + return NewPostInitRequestWithBody(server, "application/json", bodyReader) } -// NewInitSandboxRequestWithBody generates requests for InitSandbox with any type of body -func NewInitSandboxRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewPostInitRequestWithBody generates requests for PostInit with any type of body +func NewPostInitRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -719,35 +715,35 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // GetEnvVarsWithResponse request - GetEnvVarsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetEnvVarsResponse, error) + // GetEnvsWithResponse request + GetEnvsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetEnvsResponse, error) - // DownloadFileWithResponse request - DownloadFileWithResponse(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*DownloadFileResponse, error) + // GetFilesWithResponse request + GetFilesWithResponse(ctx context.Context, params *GetFilesParams, reqEditors ...RequestEditorFn) (*GetFilesResponse, error) - // UploadFileWithBodyWithResponse request with any body - UploadFileWithBodyWithResponse(ctx context.Context, params *UploadFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadFileResponse, error) + // PostFilesWithBodyWithResponse request with any body + PostFilesWithBodyWithResponse(ctx context.Context, params *PostFilesParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostFilesResponse, error) // GetHealthWithResponse request GetHealthWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetHealthResponse, error) - // InitSandboxWithBodyWithResponse request with any body - InitSandboxWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*InitSandboxResponse, error) + // PostInitWithBodyWithResponse request with any body + PostInitWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostInitResponse, error) - InitSandboxWithResponse(ctx context.Context, body InitSandboxJSONRequestBody, reqEditors ...RequestEditorFn) (*InitSandboxResponse, error) + PostInitWithResponse(ctx context.Context, body PostInitJSONRequestBody, reqEditors ...RequestEditorFn) (*PostInitResponse, error) // GetMetricsWithResponse request GetMetricsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetMetricsResponse, error) } -type GetEnvVarsResponse struct { +type GetEnvsResponse struct { Body []byte HTTPResponse *http.Response JSON200 *EnvVars } // Status returns HTTPResponse.Status -func (r GetEnvVarsResponse) Status() string { +func (r GetEnvsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -755,25 +751,24 @@ func (r GetEnvVarsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetEnvVarsResponse) StatusCode() int { +func (r GetEnvsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DownloadFileResponse struct { +type GetFilesResponse struct { Body []byte HTTPResponse *http.Response JSON400 *InvalidPath JSON401 *InvalidUser JSON404 *FileNotFound - JSON406 *NotAcceptable JSON500 *InternalServerError } // Status returns HTTPResponse.Status -func (r DownloadFileResponse) Status() string { +func (r GetFilesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -781,14 +776,14 @@ func (r DownloadFileResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DownloadFileResponse) StatusCode() int { +func (r GetFilesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type UploadFileResponse struct { +type PostFilesResponse struct { Body []byte HTTPResponse *http.Response JSON200 *UploadSuccess @@ -799,7 +794,7 @@ type UploadFileResponse struct { } // Status returns HTTPResponse.Status -func (r UploadFileResponse) Status() string { +func (r PostFilesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -807,7 +802,7 @@ func (r UploadFileResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UploadFileResponse) StatusCode() int { +func (r PostFilesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -835,13 +830,13 @@ func (r GetHealthResponse) StatusCode() int { return 0 } -type InitSandboxResponse struct { +type PostInitResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r InitSandboxResponse) Status() string { +func (r PostInitResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -849,7 +844,7 @@ func (r InitSandboxResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r InitSandboxResponse) StatusCode() int { +func (r PostInitResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -878,31 +873,31 @@ func (r GetMetricsResponse) StatusCode() int { return 0 } -// GetEnvVarsWithResponse request returning *GetEnvVarsResponse -func (c *ClientWithResponses) GetEnvVarsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetEnvVarsResponse, error) { - rsp, err := c.GetEnvVars(ctx, reqEditors...) +// GetEnvsWithResponse request returning *GetEnvsResponse +func (c *ClientWithResponses) GetEnvsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetEnvsResponse, error) { + rsp, err := c.GetEnvs(ctx, reqEditors...) if err != nil { return nil, err } - return ParseGetEnvVarsResponse(rsp) + return ParseGetEnvsResponse(rsp) } -// DownloadFileWithResponse request returning *DownloadFileResponse -func (c *ClientWithResponses) DownloadFileWithResponse(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*DownloadFileResponse, error) { - rsp, err := c.DownloadFile(ctx, params, reqEditors...) +// GetFilesWithResponse request returning *GetFilesResponse +func (c *ClientWithResponses) GetFilesWithResponse(ctx context.Context, params *GetFilesParams, reqEditors ...RequestEditorFn) (*GetFilesResponse, error) { + rsp, err := c.GetFiles(ctx, params, reqEditors...) if err != nil { return nil, err } - return ParseDownloadFileResponse(rsp) + return ParseGetFilesResponse(rsp) } -// UploadFileWithBodyWithResponse request with arbitrary body returning *UploadFileResponse -func (c *ClientWithResponses) UploadFileWithBodyWithResponse(ctx context.Context, params *UploadFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadFileResponse, error) { - rsp, err := c.UploadFileWithBody(ctx, params, contentType, body, reqEditors...) +// PostFilesWithBodyWithResponse request with arbitrary body returning *PostFilesResponse +func (c *ClientWithResponses) PostFilesWithBodyWithResponse(ctx context.Context, params *PostFilesParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostFilesResponse, error) { + rsp, err := c.PostFilesWithBody(ctx, params, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseUploadFileResponse(rsp) + return ParsePostFilesResponse(rsp) } // GetHealthWithResponse request returning *GetHealthResponse @@ -914,21 +909,21 @@ func (c *ClientWithResponses) GetHealthWithResponse(ctx context.Context, reqEdit return ParseGetHealthResponse(rsp) } -// InitSandboxWithBodyWithResponse request with arbitrary body returning *InitSandboxResponse -func (c *ClientWithResponses) InitSandboxWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*InitSandboxResponse, error) { - rsp, err := c.InitSandboxWithBody(ctx, contentType, body, reqEditors...) +// PostInitWithBodyWithResponse request with arbitrary body returning *PostInitResponse +func (c *ClientWithResponses) PostInitWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostInitResponse, error) { + rsp, err := c.PostInitWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseInitSandboxResponse(rsp) + return ParsePostInitResponse(rsp) } -func (c *ClientWithResponses) InitSandboxWithResponse(ctx context.Context, body InitSandboxJSONRequestBody, reqEditors ...RequestEditorFn) (*InitSandboxResponse, error) { - rsp, err := c.InitSandbox(ctx, body, reqEditors...) +func (c *ClientWithResponses) PostInitWithResponse(ctx context.Context, body PostInitJSONRequestBody, reqEditors ...RequestEditorFn) (*PostInitResponse, error) { + rsp, err := c.PostInit(ctx, body, reqEditors...) if err != nil { return nil, err } - return ParseInitSandboxResponse(rsp) + return ParsePostInitResponse(rsp) } // GetMetricsWithResponse request returning *GetMetricsResponse @@ -940,15 +935,15 @@ func (c *ClientWithResponses) GetMetricsWithResponse(ctx context.Context, reqEdi return ParseGetMetricsResponse(rsp) } -// ParseGetEnvVarsResponse parses an HTTP response from a GetEnvVarsWithResponse call -func ParseGetEnvVarsResponse(rsp *http.Response) (*GetEnvVarsResponse, error) { +// ParseGetEnvsResponse parses an HTTP response from a GetEnvsWithResponse call +func ParseGetEnvsResponse(rsp *http.Response) (*GetEnvsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetEnvVarsResponse{ + response := &GetEnvsResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -966,15 +961,15 @@ func ParseGetEnvVarsResponse(rsp *http.Response) (*GetEnvVarsResponse, error) { return response, nil } -// ParseDownloadFileResponse parses an HTTP response from a DownloadFileWithResponse call -func ParseDownloadFileResponse(rsp *http.Response) (*DownloadFileResponse, error) { +// ParseGetFilesResponse parses an HTTP response from a GetFilesWithResponse call +func ParseGetFilesResponse(rsp *http.Response) (*GetFilesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DownloadFileResponse{ + response := &GetFilesResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -1001,13 +996,6 @@ func ParseDownloadFileResponse(rsp *http.Response) (*DownloadFileResponse, error } response.JSON404 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 406: - var dest NotAcceptable - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON406 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -1020,15 +1008,15 @@ func ParseDownloadFileResponse(rsp *http.Response) (*DownloadFileResponse, error return response, nil } -// ParseUploadFileResponse parses an HTTP response from a UploadFileWithResponse call -func ParseUploadFileResponse(rsp *http.Response) (*UploadFileResponse, error) { +// ParsePostFilesResponse parses an HTTP response from a PostFilesWithResponse call +func ParsePostFilesResponse(rsp *http.Response) (*PostFilesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UploadFileResponse{ + response := &PostFilesResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -1090,15 +1078,15 @@ func ParseGetHealthResponse(rsp *http.Response) (*GetHealthResponse, error) { return response, nil } -// ParseInitSandboxResponse parses an HTTP response from a InitSandboxWithResponse call -func ParseInitSandboxResponse(rsp *http.Response) (*InitSandboxResponse, error) { +// ParsePostInitResponse parses an HTTP response from a PostInitWithResponse call +func ParsePostInitResponse(rsp *http.Response) (*PostInitResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &InitSandboxResponse{ + response := &PostInitResponse{ Body: bodyBytes, HTTPResponse: rsp, } diff --git a/tests/integration/internal/tests/api/sandboxes/sandbox_auto_pause_test.go b/tests/integration/internal/tests/api/sandboxes/sandbox_auto_pause_test.go index a3b31495e2..e8502b56b6 100644 --- a/tests/integration/internal/tests/api/sandboxes/sandbox_auto_pause_test.go +++ b/tests/integration/internal/tests/api/sandboxes/sandbox_auto_pause_test.go @@ -88,10 +88,10 @@ func TestSandboxAutoPauseResumePersisted(t *testing.T) { require.NoError(t, err) // Check if the file is still there after resuming - fileResponse, err := envdClient.HTTPClient.DownloadFileWithResponse( + fileResponse, err := envdClient.HTTPClient.GetFilesWithResponse( t.Context(), - &envd.DownloadFileParams{ - Path: path, + &envd.GetFilesParams{ + Path: &path, Username: sharedUtils.ToPtr("user"), }, setup.WithSandbox(t, sbxId), @@ -128,10 +128,10 @@ func TestSandboxAutoPauseResumePersisted(t *testing.T) { assert.Equal(t, sbxResume.JSON201.SandboxID, sbxId) // Check if the file is still there after resuming - fileResponse, err = envdClient.HTTPClient.DownloadFileWithResponse( + fileResponse, err = envdClient.HTTPClient.GetFilesWithResponse( t.Context(), - &envd.DownloadFileParams{ - Path: path, + &envd.GetFilesParams{ + Path: &path, Username: sharedUtils.ToPtr("user"), }, setup.WithSandbox(t, sbxId), diff --git a/tests/integration/internal/tests/api/templates/template_tags_test.go b/tests/integration/internal/tests/api/templates/template_tags_test.go index 48f135adc5..3e964dc66c 100644 --- a/tests/integration/internal/tests/api/templates/template_tags_test.go +++ b/tests/integration/internal/tests/api/templates/template_tags_test.go @@ -550,9 +550,9 @@ func TestAssignmentOrderingLatestWins(t *testing.T) { // Read the version file from the sandbox to verify it's using the latest build envdClient := setup.GetEnvdClient(t, ctx) - fileResp, err := envdClient.HTTPClient.DownloadFileWithResponse( + fileResp, err := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: versionFilePath, Username: utils.ToPtr("user")}, + &envd.GetFilesParams{Path: &versionFilePath, Username: utils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) require.NoError(t, err) @@ -619,9 +619,9 @@ func TestAssignmentOrderingAfterTagReassignment(t *testing.T) { // Read the version file from the sandbox to verify it's using the reassigned build envdClient := setup.GetEnvdClient(t, ctx) - fileResp, err := envdClient.HTTPClient.DownloadFileWithResponse( + fileResp, err := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: versionFilePath, Username: utils.ToPtr("user")}, + &envd.GetFilesParams{Path: &versionFilePath, Username: utils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) require.NoError(t, err) diff --git a/tests/integration/internal/tests/api/volumes/crud_test.go b/tests/integration/internal/tests/api/volumes/crud_test.go index 7f123550ed..14c3e3c681 100644 --- a/tests/integration/internal/tests/api/volumes/crud_test.go +++ b/tests/integration/internal/tests/api/volumes/crud_test.go @@ -110,9 +110,9 @@ func TestVolumeRoundTrip(t *testing.T) { { ctx := t.Context() envdClient := setup.GetEnvdClient(t, ctx) - readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( + readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: filePath, Username: sharedutils.ToPtr("user")}, + &envd.GetFilesParams{Path: &filePath, Username: sharedutils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) require.NoError(t, readErr) @@ -166,9 +166,9 @@ func TestVolumeRoundTrip(t *testing.T) { { ctx := t.Context() envdClient := setup.GetEnvdClient(t, ctx) - readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( + readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: filePath, Username: sharedutils.ToPtr("user")}, + &envd.GetFilesParams{Path: &filePath, Username: sharedutils.ToPtr("user")}, setup.WithSandbox(t, sbx2.SandboxID), ) require.NoError(t, readErr) @@ -187,9 +187,9 @@ func TestVolumeRoundTrip(t *testing.T) { require.NoError(t, remErr) // verify it's gone - readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( + readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: filePath, Username: sharedutils.ToPtr("user")}, + &envd.GetFilesParams{Path: &filePath, Username: sharedutils.ToPtr("user")}, setup.WithSandbox(t, sbx2.SandboxID), ) require.NoError(t, readErr) diff --git a/tests/integration/internal/tests/envd/auth_test.go b/tests/integration/internal/tests/envd/auth_test.go index 96be5ecd03..32f23497b5 100644 --- a/tests/integration/internal/tests/envd/auth_test.go +++ b/tests/integration/internal/tests/envd/auth_test.go @@ -89,7 +89,7 @@ func TestInitWithNilTokenOnSecuredSandboxReturnsUnauthorized(t *testing.T) { sandboxEnvdInitCall(t, ctx, envdInitCall{ sbx: sbx, client: envdClient, - body: envd.InitSandboxJSONRequestBody{}, + body: envd.PostInitJSONRequestBody{}, expectedResErr: nil, expectedResHttpStatus: http.StatusUnauthorized, }) @@ -111,7 +111,7 @@ func TestInitWithWrongTokenOnSecuredSandboxReturnsUnauthorized(t *testing.T) { sandboxEnvdInitCall(t, ctx, envdInitCall{ sbx: sbx, client: envdClient, - body: envd.InitSandboxJSONRequestBody{AccessToken: &wrongToken}, + body: envd.PostInitJSONRequestBody{AccessToken: &wrongToken}, expectedResErr: nil, expectedResHttpStatus: http.StatusUnauthorized, }) @@ -136,7 +136,7 @@ func TestChangeAccessAuthorizedToken(t *testing.T) { sbx: sbx, client: envdClient, authToken: envdAuthTokenA, // this is the old token used currently by envd - body: envd.InitSandboxJSONRequestBody{AccessToken: &envdAuthTokenB}, + body: envd.PostInitJSONRequestBody{AccessToken: &envdAuthTokenB}, expectedResErr: nil, expectedResHttpStatus: http.StatusUnauthorized, }) @@ -185,9 +185,9 @@ func TestAccessAuthorizedPathWithResumedSandboxWithValidAccessToken(t *testing.T require.Equal(t, http.StatusCreated, sbxResume.StatusCode()) // try to get the file with the valid access token - fileResponse, err := envdClient.HTTPClient.DownloadFileWithResponse( + fileResponse, err := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user")}, + &envd.GetFilesParams{Path: &filePath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.JSON201.SandboxID), setup.WithEnvdAccessToken(t, *sbxMeta.EnvdAccessToken), ) @@ -242,9 +242,9 @@ func TestAccessAuthorizedPathWithResumedSandboxWithoutAccessToken(t *testing.T) assert.Equal(t, http.StatusCreated, sbxResume.StatusCode()) // try to get the file with the without access token - fileResponse, err := envdClient.HTTPClient.DownloadFileWithResponse( + fileResponse, err := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user")}, + &envd.GetFilesParams{Path: &filePath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.JSON201.SandboxID), ) if err != nil { @@ -257,7 +257,7 @@ func TestAccessAuthorizedPathWithResumedSandboxWithoutAccessToken(t *testing.T) type envdInitCall struct { sbx *api.PostSandboxesResponse client *setup.EnvdClient - body envd.InitSandboxJSONRequestBody + body envd.PostInitJSONRequestBody authToken *string expectedResErr *error expectedResHttpStatus int @@ -271,7 +271,7 @@ func sandboxEnvdInitCall(t *testing.T, ctx context.Context, req envdInitCall) { envdReqSetup = append(envdReqSetup, setup.WithEnvdAccessToken(t, *req.authToken)) } - res, err := req.client.HTTPClient.InitSandboxWithResponse(ctx, req.body, envdReqSetup...) + res, err := req.client.HTTPClient.PostInitWithResponse(ctx, req.body, envdReqSetup...) if req.expectedResErr != nil { assert.Equal(t, *req.expectedResErr, err) } else { diff --git a/tests/integration/internal/tests/envd/hyperloop_test.go b/tests/integration/internal/tests/envd/hyperloop_test.go index 0d62fe1c11..c7972c1600 100644 --- a/tests/integration/internal/tests/envd/hyperloop_test.go +++ b/tests/integration/internal/tests/envd/hyperloop_test.go @@ -29,9 +29,9 @@ func TestAccessingHyperloopServerViaIP(t *testing.T) { require.NoError(t, err, "Should be able to contact hyperloop server") readPath := "output.txt" - readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( + readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: readPath, Username: sharedUtils.ToPtr("user")}, + &envd.GetFilesParams{Path: &readPath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) @@ -54,9 +54,9 @@ func TestAccessingHyperloopServerViaDomain(t *testing.T) { require.NoError(t, err, "Should be able to contact hyperloop server") readPath := "output.txt" - readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( + readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: readPath, Username: sharedUtils.ToPtr("user")}, + &envd.GetFilesParams{Path: &readPath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) @@ -79,9 +79,9 @@ func TestAccessingHyperloopServerViaIPWithBlockedInternet(t *testing.T) { require.NoError(t, err, "Should be able to contact hyperloop server") readPath := "output.txt" - readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( + readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: readPath, Username: sharedUtils.ToPtr("user")}, + &envd.GetFilesParams{Path: &readPath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.SandboxID), ) diff --git a/tests/integration/internal/tests/envd/signatures_test.go b/tests/integration/internal/tests/envd/signatures_test.go index c79ae8a5ce..309cb7b2e0 100644 --- a/tests/integration/internal/tests/envd/signatures_test.go +++ b/tests/integration/internal/tests/envd/signatures_test.go @@ -30,10 +30,10 @@ func TestDownloadFileWhenAuthIsDisabled(t *testing.T) { filePath := "test.txt" textFile, contentType := utils.CreateTextFile(t, filePath, "Hello, World!") - writeRes, err := envdClient.HTTPClient.UploadFileWithBodyWithResponse( + writeRes, err := envdClient.HTTPClient.PostFilesWithBodyWithResponse( ctx, - &envd.UploadFileParams{ - Path: filePath, + &envd.PostFilesParams{ + Path: &filePath, Username: sharedUtils.ToPtr("user"), }, contentType, @@ -44,9 +44,9 @@ func TestDownloadFileWhenAuthIsDisabled(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusOK, writeRes.StatusCode()) - getRes, err := envdClient.HTTPClient.DownloadFileWithResponse( + getRes, err := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user")}, + &envd.GetFilesParams{Path: &filePath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.JSON201.SandboxID), ) @@ -71,10 +71,10 @@ func TestDownloadFileWithoutSigningWhenAuthIsEnabled(t *testing.T) { textFile, contentType := utils.CreateTextFile(t, filePath, "Hello, World!") writeFileSigning := generateSignature(filePath, "user", "write", nil, *envdToken) - writeRes, err := envdClient.HTTPClient.UploadFileWithBodyWithResponse( + writeRes, err := envdClient.HTTPClient.PostFilesWithBodyWithResponse( ctx, - &envd.UploadFileParams{ - Path: filePath, + &envd.PostFilesParams{ + Path: &filePath, Username: sharedUtils.ToPtr("user"), Signature: &writeFileSigning, }, @@ -87,9 +87,9 @@ func TestDownloadFileWithoutSigningWhenAuthIsEnabled(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusOK, writeRes.StatusCode()) - readRes, readErr := envdClient.HTTPClient.DownloadFile( + readRes, readErr := envdClient.HTTPClient.GetFiles( ctx, - &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user")}, + &envd.GetFilesParams{Path: &filePath, Username: sharedUtils.ToPtr("user")}, setup.WithSandbox(t, sbx.JSON201.SandboxID), ) require.NoError(t, readErr) @@ -115,10 +115,10 @@ func TestDownloadFileWithSigningWhenAuthIsEnabled(t *testing.T) { writeFileSigning := generateSignature(filePath, "user", "write", nil, *envdToken) textFile, contentType := utils.CreateTextFile(t, filePath, "Hello, World!") - writeRes, err := envdClient.HTTPClient.UploadFileWithBodyWithResponse( + writeRes, err := envdClient.HTTPClient.PostFilesWithBodyWithResponse( ctx, - &envd.UploadFileParams{ - Path: filePath, + &envd.PostFilesParams{ + Path: &filePath, Username: sharedUtils.ToPtr("user"), Signature: &writeFileSigning, }, @@ -130,9 +130,9 @@ func TestDownloadFileWithSigningWhenAuthIsEnabled(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusOK, writeRes.StatusCode()) - readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( + readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{Path: filePath, Username: sharedUtils.ToPtr("user"), Signature: &readFileSigning}, + &envd.GetFilesParams{Path: &filePath, Username: sharedUtils.ToPtr("user"), Signature: &readFileSigning}, setup.WithSandbox(t, sbx.JSON201.SandboxID), ) @@ -159,10 +159,10 @@ func TestDownloadWithAlreadyExpiredToken(t *testing.T) { signatureForRead := generateSignature(filePath, "user", "read", &signatureExpiration, *envdToken) readExpiration := int(signatureExpiration) - readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( + readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{ - Path: filePath, + &envd.GetFilesParams{ + Path: &filePath, Username: sharedUtils.ToPtr("user"), Signature: &signatureForRead, SignatureExpiration: &readExpiration, @@ -193,10 +193,10 @@ func TestDownloadWithHealthyToken(t *testing.T) { signatureForRead := generateSignature(filePath, "user", "read", &signatureExpiration, *envdToken) readExpiration := int(signatureExpiration) - readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( + readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{ - Path: filePath, + &envd.GetFilesParams{ + Path: &filePath, Username: sharedUtils.ToPtr("user"), Signature: &signatureForRead, SignatureExpiration: &readExpiration, @@ -227,10 +227,10 @@ func TestAccessWithNotCorrespondingSignatureAndSignatureExpiration(t *testing.T) signatureForRead := generateSignature(filePath, "user", "read", nil, *envdToken) readExpiration := int(signatureExpiration) - readRes, readErr := envdClient.HTTPClient.DownloadFileWithResponse( + readRes, readErr := envdClient.HTTPClient.GetFilesWithResponse( ctx, - &envd.DownloadFileParams{ - Path: filePath, + &envd.GetFilesParams{ + Path: &filePath, Username: sharedUtils.ToPtr("user"), Signature: &signatureForRead, SignatureExpiration: &readExpiration, diff --git a/tests/integration/internal/utils/filesystem.go b/tests/integration/internal/utils/filesystem.go index 5f37eac0b4..012f414618 100644 --- a/tests/integration/internal/utils/filesystem.go +++ b/tests/integration/internal/utils/filesystem.go @@ -27,9 +27,9 @@ func UploadFile(tb testing.TB, ctx context.Context, sbx *api.Sandbox, envdClient reqEditors = append(reqEditors, setup.WithEnvdAccessToken(tb, *(sbx.EnvdAccessToken))) } - writeRes, err := envdClient.HTTPClient.UploadFileWithBodyWithResponse( + writeRes, err := envdClient.HTTPClient.PostFilesWithBodyWithResponse( ctx, - &envd.UploadFileParams{Path: path, Username: utils.ToPtr("user")}, + &envd.PostFilesParams{Path: &path, Username: utils.ToPtr("user")}, contentType, buffer, reqEditors..., From 01c85b05a9f9cbc6c52e46da96b7739dc282de62 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 20 Feb 2026 02:03:12 +0000 Subject: [PATCH 07/11] chore: auto-commit generated changes --- packages/envd/internal/api/api.gen.go | 32 ++++++++++----- .../internal/sandbox/envd/envd.gen.go | 32 ++++++++++----- tests/integration/internal/envd/generated.go | 40 ++++++++++++++----- 3 files changed, 74 insertions(+), 30 deletions(-) diff --git a/packages/envd/internal/api/api.gen.go b/packages/envd/internal/api/api.gen.go index 512747b05f..a1720172d9 100644 --- a/packages/envd/internal/api/api.gen.go +++ b/packages/envd/internal/api/api.gen.go @@ -67,17 +67,26 @@ type Metrics struct { // MemTotal Total virtual memory in bytes MemTotal *int `json:"mem_total,omitempty"` + // MemTotalMib Total virtual memory in MiB + MemTotalMib *int `json:"mem_total_mib,omitempty"` + // MemUsed Used virtual memory in bytes MemUsed *int `json:"mem_used,omitempty"` + // MemUsedMib Used virtual memory in MiB + MemUsedMib *int `json:"mem_used_mib,omitempty"` + // Ts Unix timestamp in UTC for current sandbox time Ts *int64 `json:"ts,omitempty"` } -// VolumeMount Volume +// VolumeMount NFS volume mount configuration type VolumeMount struct { + // NfsTarget NFS server target address NfsTarget string `json:"nfs_target"` - Path string `json:"path"` + + // Path Mount path inside the sandbox + Path string `json:"path"` } // FilePath defines model for FilePath. @@ -104,6 +113,9 @@ type InvalidPath = Error // InvalidUser defines model for InvalidUser. type InvalidUser = Error +// NotAcceptable defines model for NotAcceptable. +type NotAcceptable = Error + // NotEnoughDiskSpace defines model for NotEnoughDiskSpace. type NotEnoughDiskSpace = Error @@ -112,16 +124,16 @@ type UploadSuccess = []EntryInfo // GetFilesParams defines parameters for GetFiles. type GetFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). Path *FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } @@ -132,16 +144,16 @@ type PostFilesMultipartBody struct { // PostFilesParams defines parameters for PostFiles. type PostFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). Path *FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } diff --git a/packages/orchestrator/internal/sandbox/envd/envd.gen.go b/packages/orchestrator/internal/sandbox/envd/envd.gen.go index 73d03e999b..12060f0922 100644 --- a/packages/orchestrator/internal/sandbox/envd/envd.gen.go +++ b/packages/orchestrator/internal/sandbox/envd/envd.gen.go @@ -62,17 +62,26 @@ type Metrics struct { // MemTotal Total virtual memory in bytes MemTotal int `json:"mem_total,omitempty"` + // MemTotalMib Total virtual memory in MiB + MemTotalMib int `json:"mem_total_mib,omitempty"` + // MemUsed Used virtual memory in bytes MemUsed int `json:"mem_used,omitempty"` + // MemUsedMib Used virtual memory in MiB + MemUsedMib int `json:"mem_used_mib,omitempty"` + // Ts Unix timestamp in UTC for current sandbox time Ts int64 `json:"ts,omitempty"` } -// VolumeMount Volume +// VolumeMount NFS volume mount configuration type VolumeMount struct { + // NfsTarget NFS server target address NfsTarget string `json:"nfs_target"` - Path string `json:"path"` + + // Path Mount path inside the sandbox + Path string `json:"path"` } // FilePath defines model for FilePath. @@ -99,6 +108,9 @@ type InvalidPath = Error // InvalidUser defines model for InvalidUser. type InvalidUser = Error +// NotAcceptable defines model for NotAcceptable. +type NotAcceptable = Error + // NotEnoughDiskSpace defines model for NotEnoughDiskSpace. type NotEnoughDiskSpace = Error @@ -107,16 +119,16 @@ type UploadSuccess = []EntryInfo // GetFilesParams defines parameters for GetFiles. type GetFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). Path FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } @@ -127,16 +139,16 @@ type PostFilesMultipartBody struct { // PostFilesParams defines parameters for PostFiles. type PostFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). Path FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } diff --git a/tests/integration/internal/envd/generated.go b/tests/integration/internal/envd/generated.go index c0461908a3..a40aec0440 100644 --- a/tests/integration/internal/envd/generated.go +++ b/tests/integration/internal/envd/generated.go @@ -71,17 +71,26 @@ type Metrics struct { // MemTotal Total virtual memory in bytes MemTotal *int `json:"mem_total,omitempty"` + // MemTotalMib Total virtual memory in MiB + MemTotalMib *int `json:"mem_total_mib,omitempty"` + // MemUsed Used virtual memory in bytes MemUsed *int `json:"mem_used,omitempty"` + // MemUsedMib Used virtual memory in MiB + MemUsedMib *int `json:"mem_used_mib,omitempty"` + // Ts Unix timestamp in UTC for current sandbox time Ts *int64 `json:"ts,omitempty"` } -// VolumeMount Volume +// VolumeMount NFS volume mount configuration type VolumeMount struct { + // NfsTarget NFS server target address NfsTarget string `json:"nfs_target"` - Path string `json:"path"` + + // Path Mount path inside the sandbox + Path string `json:"path"` } // FilePath defines model for FilePath. @@ -108,6 +117,9 @@ type InvalidPath = Error // InvalidUser defines model for InvalidUser. type InvalidUser = Error +// NotAcceptable defines model for NotAcceptable. +type NotAcceptable = Error + // NotEnoughDiskSpace defines model for NotEnoughDiskSpace. type NotEnoughDiskSpace = Error @@ -116,16 +128,16 @@ type UploadSuccess = []EntryInfo // GetFilesParams defines parameters for GetFiles. type GetFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). Path *FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } @@ -136,16 +148,16 @@ type PostFilesMultipartBody struct { // PostFilesParams defines parameters for PostFiles. type PostFilesParams struct { - // Path Path to the file, URL encoded. Can be relative to user's home directory. + // Path Path to the file, URL encoded. Can be relative to the user's home directory (e.g. "file.txt" resolves to ~/file.txt). Path *FilePath `form:"path,omitempty" json:"path,omitempty"` - // Username User used for setting the owner, or resolving relative paths. + // Username User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user. Username *User `form:"username,omitempty" json:"username,omitempty"` - // Signature Signature used for file access permission verification. + // Signature HMAC signature for access verification. Required when no X-Access-Token header is provided. Format is "v1_". Signature *Signature `form:"signature,omitempty" json:"signature,omitempty"` - // SignatureExpiration Signature expiration used for defining the expiration time of the signature. + // SignatureExpiration Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter. SignatureExpiration *SignatureExpiration `form:"signature_expiration,omitempty" json:"signature_expiration,omitempty"` } @@ -764,6 +776,7 @@ type GetFilesResponse struct { JSON400 *InvalidPath JSON401 *InvalidUser JSON404 *FileNotFound + JSON406 *NotAcceptable JSON500 *InternalServerError } @@ -996,6 +1009,13 @@ func ParseGetFilesResponse(rsp *http.Response) (*GetFilesResponse, error) { } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 406: + var dest NotAcceptable + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON406 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerError if err := json.Unmarshal(bodyBytes, &dest); err != nil { From 267b32910e7a19f1bef517bc76d3c6c21ca23536 Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Fri, 20 Feb 2026 09:40:43 -0800 Subject: [PATCH 08/11] fix: protect envd paths from being overwritten during spec merge The platform API's /health (200 with auth) was overwriting envd's /health (204, no auth) because both specs define the same path and the platform API is merged second. Add protected_paths parameter to merge_specs() so envd paths are preserved. --- scripts/generate_openapi.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/scripts/generate_openapi.py b/scripts/generate_openapi.py index 4a32881c1b..d18554fcaa 100644 --- a/scripts/generate_openapi.py +++ b/scripts/generate_openapi.py @@ -319,8 +319,16 @@ def load_yaml_file(path: str) -> str: return f.read() -def merge_specs(raw_docs: list[str]) -> dict[str, Any]: - """Merge multiple raw YAML OpenAPI docs into a single spec.""" +def merge_specs(raw_docs: list[str], protected_paths: set[str] | None = None) -> dict[str, Any]: + """Merge multiple raw YAML OpenAPI docs into a single spec. + + Args: + raw_docs: Raw YAML strings to merge (order matters — later docs + overwrite earlier ones for paths and component entries). + protected_paths: Paths that should not be overwritten once set. + Used to prevent the platform API from overwriting + envd paths that share the same name (e.g. /health). + """ merged: dict[str, Any] = { "openapi": "3.1.0", "info": { @@ -343,6 +351,8 @@ def merge_specs(raw_docs: list[str]) -> dict[str, Any]: continue for path, methods in doc.get("paths", {}).items(): + if protected_paths and path in protected_paths and path in merged["paths"]: + continue merged["paths"][path] = methods for section, entries in doc.get("components", {}).items(): @@ -699,8 +709,10 @@ def main() -> None: # --- Merge everything --- # Order: envd first, then platform API (platform schemas take precedence - # for shared names like Error since they're more complete) - merged = merge_specs(envd_raw_docs + [api_doc]) + # for shared names like Error since they're more complete). + # Protect envd paths so the platform API doesn't overwrite them + # (e.g. /health exists in both but the envd version is authoritative). + merged = merge_specs(envd_raw_docs + [api_doc], protected_paths=envd_paths) # Auto-detect and fill streaming RPC endpoints streaming_rpcs = find_streaming_rpcs(ENVD_SPEC_DIR) From 1d015c6423d94b603515668e63c71859270ab14a Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Fri, 20 Feb 2026 09:56:05 -0800 Subject: [PATCH 09/11] fix: change /health response from 200 to 204 in platform API spec --- spec/openapi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/openapi.yml b/spec/openapi.yml index 07b0bda0ef..f77f754cc8 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -1739,8 +1739,8 @@ paths: get: description: Health check responses: - "200": - description: Request was successful + "204": + description: The service is healthy "401": $ref: "#/components/responses/401" From 30686a9346aaccedccea7a94472d742f1b098fdd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 20 Feb 2026 17:57:22 +0000 Subject: [PATCH 10/11] chore: auto-commit generated changes --- packages/api/internal/api/api.gen.go | 96 ++++++++++++++-------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/packages/api/internal/api/api.gen.go b/packages/api/internal/api/api.gen.go index ceab825b5e..0513b3de6e 100644 --- a/packages/api/internal/api/api.gen.go +++ b/packages/api/internal/api/api.gen.go @@ -11741,54 +11741,54 @@ var swaggerSpec = []string{ "MmYBC9erlWs9IzyPhIthnFtkhyQy7Hxqz5MO69e1okGexzGG4rdywQ6awYXQbKhVjmOoNKNbN2QBiJiR", "tph2OSjEbmpZjDeo7lcilBSgLqFHoHfg464QK5tv4G5cm3JpjkU98RXhlFxqjMagS8q5A8QHe31uTmEh", "bS2Sg42pJxEc6gA4mF0l6vaZyQ3jiMI+0pNvSoodKD9004oWHxS17OtxxwsNpuMweaGCnJcuL4w+3VgE", - "DlW7elb3oetUdl4xtlbPHhoqgkEcYqeHULSy4gchFHniVS7x1iv8X/BZuSq5Lm713Ruy0VoZr5JYFvs7", - "bncByZMkDckAqUM1cwD9SX9YjawxzOUDKmJBVf7lJQ61oI1dKm6Z0SUJAmCTb/J/+sZwYuZXIlSRAl2d", - "142YTzDKaI6jJvce/DFJ7uGV8ldOIJpfP1MqRUSexcvEqmo0mF6KggYv6DlSJ61WMRUqHSBeOO9hU7uh", - "KaSugqTWdIU1Sjc86DusV7bRuDU7ALYlGOIl3FzD2UolqVU3rzc1k7iVs6LBXuyEFp1qjCIPJrAG5ZAg", - "UjSlkXGRLZ/Jxss954T9E18Hf+Y7O7v/wFn2z4ylIbi2f8DBHMQLnISqCjBHcc4Fuibo8uwYkSRIQwL+", - "/y6GVGSqtvnRqvnPyOtMbnxZr+OR91oTeUCMO0OIcWeD96FlqvhyJS+apYWwajq1nse4SbEH2bVrpsEm", - "w7OJfE3v8gLtm32UV6ZtckQ7SXr7a/wHIaoK+5xYleva2ahdzkr5EA5jpidlSbMunnqQxjHe0tFBJESR", - "iRPQaDs6hGiBGalA4vkeuc8iKCerPbxcLFIP8pWGvFO/3O59EOP7I/Xxzc5OjZn5Xp7Qv3KiGwCdr1Xg", - "c+Z8fBxLVc5gcVll7Ac9Ct+KzP+dmi2lD7eSj7pUWgWazq1qAuNEzLIOwUC1Vo3RGevD85f61nV5tr40", - "y4vzeoHgzdbOw9aEwJVzhGVegYaGfySyaD3zE13Cr918egZ7xwviCVVSRxWgatd247qUUiVMVdVbCrfR", - "xcUxFFRKogUi94IkWsDvENgKItSF/x5Ni6sX/jRkowTAnacQAE32E5Mr+sF/KlFUU8TGRNHv9Nya3B0F", - "u+92MpYXALfKb0BqZjS53e0Y3o7DH3BVHKt0JEsfUd8ZqQvppB3lYjgScyys+IWCx9MExTSKqM4N2qJE", - "gABht0bTeKx2lqNsQHuC72VrKx1sF5QtUEVUlVQuoSpLbe5IOXxczcwN3MCA9WXuX5Xe5/Uwy9PW9x61", - "T29ZMXvAmWx9iz7iWBbpd9WRLGOgMCvK70sSZLc48q2Srz40VaUTyrS+azyfrmEJpEW2j9eApZEkXG5h", - "40C+2oS3T62uwbJqSvsgb+AR/Z2e+8yUcHaL3lDhuVb0Y4i8rCpDb/rpbQqp2vSCkxAFOLFeAuvE/Lud", - "X4a0/eWFUQkjU0b4nPCuRxo0qRxL9cqS8hMVXNcEUHW5B5LRWTHv0zy8qhEnoa5P7PC6sisXl2zY7EMp", - "fN2QTCAMlclL7g1lBO4VV377Dyljdd8rjQCTYTbKGhtVO7shhcQzoGBuoogK8u1+r6jSzkvwPtXxGaoK", - "aiX9n6+tqP2B/sq1R9C8qSE0JDopI4xTLqBwiCmrVFg39Zj/k6OiuIiAtHOm6hQ3V6yxyytrHzgtl/YO", - "eRXredA1WaSJYj4pozOa4MiaJqJTIq+Loeq5Ao5ncU+4IxN/z1QBsGo4ZKOGFWg1saO2VZk6AAYw6jRy", - "T7ngvvYQ13mUteqznncD2qpSXkXBLNCvQOkYKFKUzPRIaUK2h1SO2zATscucuZ4KZts2bW7+Tp8H8hCm", - "eYdu/pwI5QChGpZ0rcbYRhfu+vbo3kg/lhMFLWN39UncRgc4ilRBN8rlO2+ehijOI0GziOgA8PSWsDtG", - "hY4Fv7g49hHBgSqthHJu6sEZ5mWVveWl4kC2ylIqv6coJpjnOsW2WZoR/4YypQu9d8+BJVl4bAany8WV", - "0miJD3u/dC7CVtlWYdUbqx5sFkGSUF6tRMTlmjQNpGb0H+5k22JAt89e0bReM6yp6Gu/cTu8QWoevLVq", - "lMqXr4TheoF4mrOAWB4priup90RlWAoYcppj0G6P6vKJ3AsdqLwZ9VjliltWO1Yi/UU6ixTQKxKGuNth", - "QY1OtfSF/rBJf0xIQfBIN0y1oM1hsJ6ToguNFRdh+ZuFqjJEeohdwXZz6+I5Vgj0slYFHfD8alL4vkwK", - "VsHNR9kTRFmcc83GhLdD2r59Ngy594BPYnzfeciBhrSB2nXgTXpB5edqKHIYGzjB96+c4NlzAt8R08Fo", - "APke5b/ILalQCYRlaI/jliAMBtmu2p2LTbbwsoLqV94sofoVkPGVQRHVzcaRneB7m3e98qpV8yql5xok", - "O5qmTpZTfhzw2inSqrQdxMEFJq42LbPqMJZHy61mv57w9TFKmh1AV+WiqhFC3WakWgKPjjAhm8jWYf5x", - "FrUapL/dXTkMuv5Ciy2orAaIg4Bkwtjsn114xBoprMK+Jjqh4+Qb/KM9nvsACpjQac1soIQqlQhUGQw6", - "uZzOBwv/a+F41TxSWLdsv4lbarmYjpu8c91lXvv4m2TxKkuXSeK90NCPJs3h9+mL0z22U3CZHr4zcU2p", - "2sczo4puvZlVn4JsL/BsXbyzOpOcaBQDfdeSGL89C86rlYrr1OJdAbn7ujYYnv3Ef4ZiYc3aAh0X7RoJ", - "RkG2NMG8WTEgJLRBcd67eFbai1/psZseq6ztW5mEeWhurhYxsM7QKsmdR6o3iq7DfSkrualXkaHr5Yjz", - "3Y9DK3l9B/ZsEWpFqFvSULWESWwTEpdVrmCkqsMWZqngGiEv0rZVe0aWeeG635E6U1z7C1IOtBbOsb6X", - "aLUkyNLp4hr5/1tTxj3/+PrnobQ4I7pyTTJQZfEy6O3laj6+L22GLTBNFCuffNP1gx7G+GirSqp2gdRB", - "NKruoPdlma813s+mLJLjgt118zJFA3PMlc3pBySBZmhxw5pYLaUWqcDfIeJYBfdLBQwvif/NBhcHOeNg", - "OXtJ0cUu70ETGPOmPy7GbYwJKSOBUDUnh7FqSRWHRa/WgSNyS6Ixgx5DB8fWnisvtyHYn7I0bjMpwyij", - "Vqkm3pC+Fc6cnHWwztX9CLCO/PNUN7iZ6Ea0rN1slRc1t4cy1ra0sH2MVVf3firWepSE5L4s/aj5bEE4", - "radLx1hUy9O5jn46479Pp5y08LLRaRK+G267NFPcGAdqDR3p5Tyv7KaT3UCp2Mm3Oebz7oTTOEF5FqU4", - "RBFNboxWDTMoNoskxjFNrAOLF0R9GyrjfSxq2z6SATlMnXM17FBLZ6OW7iBj55v1kL7cl0vY+bb3p42X", - "uzlhELCtf1RVZhUmvgOjwHM5NsYw2uOeBObQZfTP2si1SvPBWjx4y4rLj3Th1QIM7Osafd1esu3KTro1", - "ICqoK+ns593vOYe33xakVAB6vUBpQlDKUJwylf8ddmJQjlyhjvFyCXTOhZZJ6mV6uVhAaUsp3r0kE9Jr", - "wvOnDBPsTMQ3KC9YmxbOYhEvNFXfi9Sm9T3wdsbCXOjEhuxsC8iP18ZtKKPg593l1FQ/dG7B292qy/3q", - "naQ/7z6Fm/Tn3eduLtQ78aMVWKi/sWwCbPhlddfg6/assOju+/atWAsQ7Yz01XljFdTdY0QfazJ3EvvT", - "Gc3XzONhR0Zx+Odls18jN33bdp0veXm/fZLL++1TXd4aAMP/DCCv93g/5aVRHpOhdcxNa9fbs/i0fh2m", - "mmu0+jICPUZzNU/t9bNC9Joljalzrvq4+YiF1LWUUzOY3Gyohj1rB72Y1HbNvXpJQZJroS6LdUy+qX+M", - "qpfeQnOqkaa6z3rY0SKQgWdgQEYF5yYYAzfx/eMZ1mxu0uHIUuxTqxfLOjG6s2m2YHJovBJJnSnA5OzW", - "IDVnkbfnzYXI+N5kgjO6TXavt3GWeVb/b2UuhzKVwbdaOrvqj5B3wv4b1rglJODVhqbeu/WbtlYWfxdC", - "wNXD/w8AAP//wduPstEWAQA=", + "DlW7elb3oetUdl4xtlbPHhoqgkEcYqeHULSy4gchFHniVS7x1iv8X/BZuSq5Lm713Rt0IqX8aapPcGSS", + "mI/aXUDyJElDMkDqUM0cQH/SH1Yjawxz+YCKWFCVf3mJQy1oY5eKW2Z0SYIA2OSb/J++MZyY+ZUIVaRA", + "V+d1I+YTjDKa46jJvQd/TJJ7eKX8lROI5tfPlEoRkWfxMrGqGg2ml6KgwQt6jtRJq1VMhUoHiBfOe9jU", + "bmgKqasgqTVdYY3SDQ/6DhvCSeEc6R0A2xIM8RJuruFspZLUqpvXm5pJ3MpZ0WAvdkKLTjVGkQcTWINy", + "SBApmtLIuMiWz2Tj5Z5zwv6Jr4M/852d3X/gLPtnxtIQXNs/4GAO4gVOQlUFmKM45wJdE3R5doxIEqQh", + "Af9/F0MqMlXb/GjV/GfkdSY3vqzX8ch7rYk8IMadIcS4s8H70DJVfLmSF83SQlg1nVrPY9yk2IPs2jXT", + "YJPh2US+pnd5gfbNPsor0zpkSytJevtr/Achqgr7nFiV69rZqF3OSvkQDmOmJ2VJsy6eepDGMd7S0UEk", + "RJGJE9BoOzqEaIEZqUDi+R65zyIoJ6s9vFwsUg/ylYa8U7/c7n0Q4/sj9fHNzk6NmflentC/cqIbAJ2v", + "VeBz5nx8HEtVzmBxWWXsBz0K34rM/52aLaUPt5KPulRaBZrOrWoC40TMsg7BQLVWjdEZ68Pzl/rWdXm2", + "vjTLi/N6geDN1s7D1oTAlXOEZV6BhoZ/JLJoPfMTXcKv3Xx6BnvHC+IJVVJHFaBq13bjupRSJUxV1VsK", + "t9HFxTEUVEqiBSL3giRawO8Q2Aoi1IX/Hk2Lqxf+NGSjBMCdpxAATfYTkyv6wX8qUVRTxMZE0e/03Jrc", + "HQW773YylhcAt8pvQGpmNLnd7RjejsMfcFUcq3QkSx9R3xmpC+mkHeViOBJzLKz4hYLH0wTFNIqozg3a", + "okSAAGG3RtN4rHaWo2xAe4LvZWsrHWwXlC1QRVSVVC6hKktt7kg5fFzNzA3cwID1Ze5fld7n9TDL09b3", + "HrVPb1kxe8CZbH2LPuJYFul31ZEsY6AwK8rvSxJktzjyrZKvPjRVpRPKtL5rPJ+uYQmkRbaP14ClkSRc", + "bmHjQL7ahLdPra7BsmpK+yBv4BH9nZ77zJRwdoveUOG5VvRjiLysKkNv+ultCqna9IKTEAU4sV4C68T8", + "u51fhrT95YVRCSNTRvic8K5HGjSpHEv1ypLyExVc1wRQdbkHktFZMe/TPLyqESehrk/s8LqyKxeXbNjs", + "Qyl83ZBMIAyVyUvuDWUE7hVXfvsPKWN13yuNAJNhNsoaG1U7uyGFxDOgYG6iiAry7X6vqNLOS/A+1fEZ", + "qgpqJf2fr62o/YH+yrVH0LypITQkOikjjFMuoHCIKatUWDf1mP+To6K4iIC0c6bqFDdXrLHLK2sfOC2X", + "9g55Fet50DVZpIliPimjM5rgyJomolMir4uh6rkCjmdxT7gjE3/PVAGwajhko4YVaDWxo7ZVmToABjDq", + "NHJPueC+9hDXeZS16rOedwPaqlJeRcEs0K9A6RgoUpTM9EhpQraHVI7bMBOxy5y5ngpm2zZtbv5Onwfy", + "EKZ5h27+nAjlAKEalnStxthGF+769ujeSD+WEwUtY3f1SdxGBziKVEE3yuU7b56GKM4jQbOI6ADw9Jaw", + "O0aFjgW/uDj2EcGBKq2Ecm7qwRnmZZW95aXiQLbKUiq/pygmmOc6xbZZmhH/hjKlC713z4ElWXhsBqfL", + "xZXSaIkPe790LsJW2VZh1RurHmwWQZJQXq1ExOWaNA2kZvQf7mTbYkC3z17RtF4zrKnoa79xO7xBah68", + "tWqUypevhOF6gXias4BYHimuK6n3RGVYChhymmPQbo/q8oncCx2ovBn1WOWKW1Y7ViL9RTqLFNArEoa4", + "22FBjU619IX+sEl/TEhB8Eg3TLWgzWGwnpOiC40VF2H5m4WqMkR6iF3BdnPr4jlWCPSyVgUd8PxqUvi+", + "TApWwc1H2RNEWZxzzcaEt0Pavn02DLn3gE9ifN95yIGGtIHadeBNekHl52oochgbOMH3r5zg2XMC3xHT", + "wWgA+R7lv8gtqVAJhGVoj+OWIAwG2a7anYtNtvCygupX3iyh+hWQ8ZVBEdXNxpGd4Hubd73yqlXzKqXn", + "GiQ7mqZOllN+HPDaKdKqtB3EwQUmrjYts+owlkfLrWa/nvD1MUqaHUBX5aKqEULdZqRaAo+OMCGbyNZh", + "/nEWtRqkv91dOQy6/kKLLaisBoiDgGTC2OyfXXjEGimswr4mOqHj5Bv8oz2e+wAKmNBpzWyghCqVCFQZ", + "DDq5nM4HC/9r4XjVPFJYt2y/iVtquZiOm7xz3WVe+/ibZPEqS5dJ4r3Q0I8mzeH36YvTPbZTcJkevjNx", + "TanaxzOjim69mVWfgmwv8GxdvLM6k5xoFAN915IYvz0LzquViuvU4l0Bufu6Nhie/cR/hmJhzdoCHRft", + "GglGQbY0wbxZMSAktEFx3rt4VtqLX+mxmx6rrO1bmYR5aG6uFjGwztAqyZ1HqjeKrsN9KSu5qVeRoevl", + "iPPdj0MreX0H9mwRakWoW9JQtYRJbBMSl1WuYKSqwxZmqeAaIS/StlV7RpZ54brfkTpTXPsLUg60Fs6x", + "vpdotSTI0uniGvn/W1PGPf/4+uehtDgjunJNMlBl8TLo7eVqPr4vbYYtME0UK5980/WDHsb4aKtKqnaB", + "1EE0qu6g92WZrzXez6YskuOC3XXzMkUDc8yVzekHJIFmaHHDmlgtpRapwN8h4lgF90sFDC+J/80GFwc5", + "42A5e0nRxS7vQRMY86Y/LsZtjAkpI4FQNSeHsWpJFYdFr9aBI3JLojGDHkMHx9aeKy+3IdifsjRuMynD", + "KKNWqSbekL4VzpycdbDO1f0IsI7881Q3uJnoRrSs3WyVFzW3hzLWtrSwfYxVV/d+KtZ6lITkviz9qPls", + "QTitp0vHWFTL07mOfjrjv0+nnLTwstFpEr4bbrs0U9wYB2oNHenlPK/sppPdQKnYybc55vPuhNM4QXkW", + "pThEEU1ujFYNMyg2iyTGMU2sA4sXRH0bKuN9LGrbPpIBOUydczXsUEtno5buIGPnm/WQvtyXS9j5tven", + "jZe7OWEQsK1/VFVmFSa+A6PAczk2xjDa454E5tBl9M/ayLVK88FaPHjLisuPdOHVAgzs6xp93V6y7cpO", + "ujUgKqgr6ezn3e85h7ffFqRUAHq9QGlCUMpQnDKV/x12YlCOXKGO8XIJdM6FlknqZXq5WEBpSynevSQT", + "0mvC86cME+xMxDcoL1ibFs5iES80Vd+L1Kb1PfB2xsJc6MSG7GwLyI/Xxm0oo+Dn3eXUVD90bsHb3arL", + "/eqdpD/vPoWb9Ofd524u1DvxoxVYqL+xbAJs+GV11+Dr9qyw6O779q1YCxDtjPTVeWMV1N1jRB9rMncS", + "+9MZzdfM42FHRnH452WzXyM3fdt2nS95eb99ksv77VNd3hoAw/8MIK/3eD/lpVEek6F1zE1r19uz+LR+", + "Haaaa7T6MgI9RnM1T+31s0L0miWNqXOu+rj5iIXUtZRTM5jcbKiGPWsHvZjUds29eklBkmuhLot1TL6p", + "f4yql95Cc6qRprrPetjRIpCBZ2BARgXnJhgDN/H94xnWbG7S4chS7FOrF8s6MbqzabZgcmi8EkmdKcDk", + "7NYgNWeRt+fNhcj43mSCM7pNdq+3cZZ5Vv9vZS6HMpXBt1o6u+qPkHfC/hvWuCUk4NWGpt679Zu2VhZ/", + "F0LA1cP/DwAA//8aKX0Z0RYBAA==", } // GetSwagger returns the content of the embedded swagger specification file From 183dad6270af9a6747c3ae54e136ee480504409b Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Fri, 20 Feb 2026 10:54:47 -0800 Subject: [PATCH 11/11] fix: correct spec to match live API responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. MemoryMB minimum 128 → 0 (API returns 0 for some templates) 2. CPUCount minimum 1 → 0 (API returns 0 for some templates) 3. TemplateBuildStatus enum: add uploaded, failed, "" values 4. TemplateLegacy: add buildStatus and names properties 5. volumeMounts: remove from required in SandboxDetail and ListedSandbox 6. LogLevel enum: add "" (Go zero-value) --- spec/openapi.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/spec/openapi.yml b/spec/openapi.yml index f77f754cc8..168a1b269d 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -205,13 +205,13 @@ components: CPUCount: type: integer format: int32 - minimum: 1 + minimum: 0 description: CPU cores for the sandbox MemoryMB: type: integer format: int32 - minimum: 128 + minimum: 0 description: Memory for the sandbox in MiB DiskSizeMB: @@ -467,7 +467,6 @@ components: - endAt - state - envdVersion - - volumeMounts properties: templateID: type: string @@ -526,7 +525,6 @@ components: - endAt - state - envdVersion - - volumeMounts properties: templateID: type: string @@ -870,6 +868,13 @@ components: description: Number of times the template was built envdVersion: $ref: "#/components/schemas/EnvdVersion" + buildStatus: + $ref: "#/components/schemas/TemplateBuildStatus" + names: + type: array + nullable: true + items: + type: string TemplateBuild: required: @@ -1169,6 +1174,7 @@ components: - info - warn - error + - "" BuildLogEntry: required: @@ -1214,6 +1220,9 @@ components: - waiting - ready - error + - uploaded + - failed + - "" TemplateBuildInfo: required: