-
Notifications
You must be signed in to change notification settings - Fork 737
feat(sdk): Go SDK with oapi-codegen for Lifecycle, Execd, and Egress APIs #597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
AlexandrePh
wants to merge
16
commits into
alibaba:main
Choose a base branch
from
AlexandrePh:feat/go-sdk
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 12 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
254cd8f
feat(sdk): Go SDK with oapi-codegen for Lifecycle, Execd, and Egress …
AlexandrePh 61a8b7f
feat(sdk): add high-level Sandbox API, SandboxManager, and Go e2e tests
AlexandrePh ebdb986
refactor(sdk): simplify code from review — DRY config, strings.Builde…
AlexandrePh f2dfd1a
feat(sdk): expand e2e coverage — filesystem CRUD, sessions, connect, …
AlexandrePh e37f1f8
feat(sdk): add CodeInterpreter wrapper + 6 e2e tests
AlexandrePh 7de8373
feat(sdk): close e2e gaps — env injection, sessions, volumes, network…
AlexandrePh 6fe1ad8
feat(sdk): add real scenario e2e tests — LLM agent in sandbox
AlexandrePh 7fa5d42
docs(sdk): add Go SDK examples — agent loop + code interpreter agent
AlexandrePh dece184
style(sdk): run gofmt on Go SDK files
AlexandrePh 8440231
fix(sdk): address code review — error handling, race condition, doc a…
AlexandrePh 7959da0
fix(sdk): replace sync.Once with mutex for endpoint resolution
AlexandrePh 5a83b20
test(sdk): add concurrent sandbox creation test + PVC volume test now…
AlexandrePh 9d6a77c
fix(sdk): address Copilot review — nested events, unused config, test…
AlexandrePh d83a932
fix(sdk): parse nested error/results objects per execd OpenAPI spec
AlexandrePh d50d837
docs(sdk): fix README streaming example and stale API signatures
AlexandrePh 22f8493
fix(sdk): preserve endpoint headers and apply custom headers to strea…
AlexandrePh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| .PHONY: all build vet test lint generate | ||
|
|
||
| all: build vet test | ||
|
|
||
| generate: | ||
| cd opensandbox/api/lifecycle && oapi-codegen --config cfg.yaml ../specs/sandbox-lifecycle.yml | ||
| cd opensandbox/api/execd && oapi-codegen --config cfg.yaml ../specs/execd-api.yaml | ||
| cd opensandbox/api/egress && oapi-codegen --config cfg.yaml ../specs/egress-api.yaml | ||
| @echo "Generated all API clients from OpenAPI specs" | ||
|
|
||
| build: | ||
| go build ./... | ||
|
|
||
| vet: | ||
| go vet ./... | ||
|
|
||
| test: | ||
| go test ./opensandbox/ -v | ||
|
|
||
| test-integration: | ||
| go test -tags=integration ./opensandbox/ -v -timeout 3m | ||
|
|
||
| test-staging: | ||
| go test -tags=staging ./opensandbox/ -v -timeout 3m | ||
|
|
||
| lint: | ||
| @which staticcheck >/dev/null 2>&1 && staticcheck ./... || echo "staticcheck not installed — skipping lint" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,237 @@ | ||
| # OpenSandbox Go SDK | ||
|
|
||
| Go client library for the [OpenSandbox](https://github.com/alibaba/OpenSandbox) API. | ||
|
|
||
| Covers all three OpenAPI specs: | ||
| - **Lifecycle** — Create, manage, and destroy sandbox instances | ||
| - **Execd** — Execute commands, manage files, monitor metrics inside sandboxes | ||
| - **Egress** — Inspect and mutate sandbox network policy at runtime | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| go get github.com/alibaba/OpenSandbox/sdks/sandbox/go | ||
| ``` | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### Create and manage a sandbox | ||
|
|
||
| ```go | ||
| package main | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "log" | ||
|
|
||
| "github.com/alibaba/OpenSandbox/sdks/sandbox/go/opensandbox" | ||
| ) | ||
|
|
||
| func main() { | ||
| ctx := context.Background() | ||
|
|
||
| // Create a lifecycle client | ||
| lc := opensandbox.NewLifecycleClient("http://localhost:8080/v1", "your-api-key") | ||
|
|
||
| // Create a sandbox | ||
| sbx, err := lc.CreateSandbox(ctx, opensandbox.CreateSandboxRequest{ | ||
| Image: opensandbox.ImageSpec{URI: "python:3.12"}, | ||
| Entrypoint: []string{"/bin/sh"}, | ||
| ResourceLimits: opensandbox.ResourceLimits{ | ||
| "cpu": "500m", | ||
| "memory": "512Mi", | ||
| }, | ||
| }) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| fmt.Printf("Created sandbox: %s (state: %s)\n", sbx.ID, sbx.Status.State) | ||
|
|
||
| // Get sandbox details | ||
| sbx, err = lc.GetSandbox(ctx, sbx.ID) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
|
|
||
| // List all running sandboxes | ||
| list, err := lc.ListSandboxes(ctx, opensandbox.ListOptions{ | ||
| States: []opensandbox.SandboxState{opensandbox.StateRunning}, | ||
| PageSize: 10, | ||
| }) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| fmt.Printf("Running sandboxes: %d\n", list.Pagination.TotalItems) | ||
|
|
||
| // Pause and resume | ||
| _ = lc.PauseSandbox(ctx, sbx.ID) | ||
| _ = lc.ResumeSandbox(ctx, sbx.ID) | ||
|
|
||
| // Clean up | ||
| _ = lc.DeleteSandbox(ctx, sbx.ID) | ||
| } | ||
| ``` | ||
|
|
||
| ### Run a command with streaming output | ||
|
|
||
| ```go | ||
| exec := opensandbox.NewExecdClient("http://localhost:9090", "your-execd-token") | ||
|
|
||
| err := exec.RunCommand(ctx, opensandbox.RunCommandRequest{ | ||
| Command: "echo 'Hello from sandbox!'", | ||
| Timeout: 30000, | ||
| }, func(event opensandbox.StreamEvent) error { | ||
| switch event.Event { | ||
| case "stdout": | ||
| fmt.Print(event.Data) | ||
| case "stderr": | ||
| fmt.Fprintf(os.Stderr, "%s", event.Data) | ||
| case "result": | ||
| fmt.Printf("\n[done] %s\n", event.Data) | ||
| } | ||
| return nil | ||
| }) | ||
| ``` | ||
|
|
||
| ### Check egress policy | ||
|
|
||
| ```go | ||
| egress := opensandbox.NewEgressClient("http://localhost:18080", "your-egress-token") | ||
|
|
||
| // Get current policy | ||
| policy, err := egress.GetPolicy(ctx) | ||
| fmt.Printf("Mode: %s, Default: %s\n", policy.Mode, policy.Policy.DefaultAction) | ||
|
|
||
| // Add a rule | ||
| updated, err := egress.PatchPolicy(ctx, []opensandbox.NetworkRule{ | ||
| {Action: "allow", Target: "api.example.com"}, | ||
| }) | ||
| ``` | ||
|
|
||
| ## API Reference | ||
|
|
||
| ### LifecycleClient | ||
|
|
||
| Created with `NewLifecycleClient(baseURL, apiKey string, opts ...Option)`. | ||
|
|
||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `CreateSandbox(ctx, req)` | Create a new sandbox from a container image | | ||
| | `GetSandbox(ctx, id)` | Get sandbox details by ID | | ||
| | `ListSandboxes(ctx, opts)` | List sandboxes with filtering and pagination | | ||
| | `DeleteSandbox(ctx, id)` | Delete a sandbox | | ||
| | `PauseSandbox(ctx, id)` | Pause a running sandbox | | ||
| | `ResumeSandbox(ctx, id)` | Resume a paused sandbox | | ||
| | `RenewExpiration(ctx, id, duration)` | Extend sandbox expiration time | | ||
| | `GetEndpoint(ctx, sandboxID, port)` | Get public endpoint for a sandbox port | | ||
|
|
||
| ### ExecdClient | ||
|
|
||
| Created with `NewExecdClient(baseURL, accessToken string, opts ...Option)`. | ||
|
|
||
| **Health:** | ||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `Ping(ctx)` | Check server health | | ||
|
|
||
| **Code Execution:** | ||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `ListContexts(ctx, language)` | List active code execution contexts | | ||
| | `CreateContext(ctx, req)` | Create a code execution context | | ||
| | `GetContext(ctx, contextID)` | Get context details | | ||
| | `DeleteContext(ctx, contextID)` | Delete a context | | ||
| | `DeleteContextsByLanguage(ctx, language)` | Delete all contexts for a language | | ||
| | `ExecuteCode(ctx, req, handler)` | Execute code with SSE streaming | | ||
| | `InterruptCode(ctx, sessionID)` | Interrupt running code | | ||
|
|
||
| **Command Execution:** | ||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `CreateSession(ctx)` | Create a bash session | | ||
| | `RunInSession(ctx, sessionID, req, handler)` | Run command in session with SSE | | ||
| | `DeleteSession(ctx, sessionID)` | Delete a bash session | | ||
| | `RunCommand(ctx, req, handler)` | Run a command with SSE streaming | | ||
| | `InterruptCommand(ctx, sessionID)` | Interrupt running command | | ||
| | `GetCommandStatus(ctx, commandID)` | Get command execution status | | ||
| | `GetCommandLogs(ctx, commandID, cursor)` | Get command stdout/stderr | | ||
|
|
||
| **File Operations:** | ||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `GetFileInfo(ctx, path)` | Get file metadata | | ||
| | `DeleteFiles(ctx, paths)` | Delete files | | ||
| | `SetPermissions(ctx, req)` | Change file permissions | | ||
| | `MoveFiles(ctx, req)` | Move/rename files | | ||
| | `SearchFiles(ctx, dir, pattern)` | Search files by glob pattern | | ||
| | `ReplaceInFiles(ctx, req)` | Text replacement in files | | ||
| | `UploadFile(ctx, localPath, remotePath)` | Upload a file to the sandbox | | ||
| | `DownloadFile(ctx, remotePath, rangeHeader)` | Download a file from the sandbox | | ||
|
|
||
| **Directory Operations:** | ||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `CreateDirectory(ctx, path, mode)` | Create a directory (mkdir -p) | | ||
| | `DeleteDirectory(ctx, path, recursive)` | Delete a directory | | ||
|
|
||
| **Metrics:** | ||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `GetMetrics(ctx)` | Get system resource metrics | | ||
| | `WatchMetrics(ctx, handler)` | Stream metrics via SSE | | ||
|
|
||
| ### EgressClient | ||
|
|
||
| Created with `NewEgressClient(baseURL, authToken string, opts ...Option)`. | ||
|
|
||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `GetPolicy(ctx)` | Get current egress policy | | ||
| | `PatchPolicy(ctx, rules)` | Merge rules into current policy | | ||
|
|
||
| ## SSE Streaming | ||
|
|
||
| Methods that stream output (`RunCommand`, `ExecuteCode`, `RunInSession`, `WatchMetrics`) accept an `EventHandler` callback: | ||
|
|
||
| ```go | ||
| type EventHandler func(event StreamEvent) error | ||
| ``` | ||
|
|
||
| Each `StreamEvent` contains: | ||
| - `Event` — the event type (e.g. `"stdout"`, `"stderr"`, `"result"`) | ||
| - `Data` — the event payload (multi-line data fields are joined with `\n`) | ||
| - `ID` — optional event identifier | ||
|
|
||
| Return a non-nil error from the handler to stop processing the stream early. | ||
|
|
||
| ## Client Options | ||
|
|
||
| All client constructors accept optional `Option` functions: | ||
|
|
||
| ```go | ||
| // Use a custom http.Client | ||
| client := opensandbox.NewLifecycleClient(url, key, | ||
| opensandbox.WithHTTPClient(myHTTPClient), | ||
| ) | ||
|
|
||
| // Set a custom timeout | ||
| client := opensandbox.NewExecdClient(url, token, | ||
| opensandbox.WithTimeout(60 * time.Second), | ||
| ) | ||
| ``` | ||
|
|
||
| ## Error Handling | ||
|
|
||
| Non-2xx responses are returned as `*opensandbox.APIError`: | ||
|
|
||
| ```go | ||
| _, err := lc.GetSandbox(ctx, "nonexistent") | ||
| if apiErr, ok := err.(*opensandbox.APIError); ok { | ||
| fmt.Printf("HTTP %d: %s — %s\n", apiErr.StatusCode, apiErr.Response.Code, apiErr.Response.Message) | ||
| } | ||
| ``` | ||
|
|
||
| ## License | ||
|
|
||
| Apache 2.0 | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.