From 6250fa2ca613ebe050dc2ea5ddf459c16bd3e45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Wed, 4 Nov 2026 09:18:07 +0100 Subject: [PATCH] chore(sdk/go): deprecate the in-tree Go SDK and drop its CI gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Go SDK moved to github.com/ai-openshell/sdk-go (#5031). Mark the old module deprecated so pkg.go.dev and go get point users there, and replace the README with a moved notice and migration steps. Drop the go job from Branch Checks and go:ci from the ci aggregate, so proto changes no longer need regenerated Go bindings in this repository. Add a Go SDK Freeze job that fails when a PR adds or changes files under sdk/go. Deletions (the removal PR) and the README and go.mod notices are allowed. The directory itself is removed in a follow-up once the final deprecated sdk/go tag exists. Signed-off-by: Roland Huß --- .github/workflows/branch-checks.yml | 43 ++-- AGENTS.md | 8 +- sdk/go/README.md | 353 +--------------------------- sdk/go/go.mod | 1 + tasks/ci.toml | 2 +- 5 files changed, 42 insertions(+), 365 deletions(-) diff --git a/.github/workflows/branch-checks.yml b/.github/workflows/branch-checks.yml index a62d442bc9..2638f415be 100644 --- a/.github/workflows/branch-checks.yml +++ b/.github/workflows/branch-checks.yml @@ -601,25 +601,38 @@ jobs: - name: Test release qualification summary run: mise run test:qualification-summary - go: - name: Go SDK + sdk-go-freeze: + # The Go SDK moved to ai-openshell/sdk-go (#5031). Its directory stays only + # until the final deprecated sdk/go tag exists, so PRs must not add or change + # files in it. Deleting files is allowed (the removal PR), and so are the + # moved notice and the deprecation comment in README.md and go.mod. + # Merge queue runs skip this job; the PR already passed it on its mirror. + name: Go SDK Freeze needs: pr_metadata - if: needs.pr_metadata.outputs.should_run == 'true' - runs-on: linux-amd64-cpu8 - timeout-minutes: 30 - container: - image: ghcr.io/nvidia/openshell/ci:9cb72baa2e61a1b5f12407e6e82da7fdba0aa722@sha256:67a9a0c32cb99825e6d3e9d9eec45d67149ea1ff7c11a1b1e1b3480d2d3df684 - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + if: needs.pr_metadata.outputs.should_run == 'true' && github.event_name == 'push' + runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false - - name: Install tools - run: mise install --locked - - - name: Lint, build, test, proto-check - run: mise run go:ci + - name: Reject changes to sdk/go + env: + BASE_SHA: ${{ needs.pr_metadata.outputs.base_sha }} + run: | + set -euo pipefail + changed=$(git diff --name-only --diff-filter=d "${BASE_SHA}...HEAD" -- \ + sdk/go ':!sdk/go/README.md' ':!sdk/go/go.mod') + if [ -z "$changed" ]; then + echo "No added or changed files under sdk/go." + exit 0 + fi + echo "::error title=Go SDK moved::The Go SDK moved to https://github.com/ai-openshell/sdk-go. Drop the sdk/go changes from this PR and open SDK changes there. Regenerated bindings are not needed here; sdk-go regenerates them from core releases." + echo "Changed files under sdk/go:" + echo "$changed" + exit 1 markdown: name: Markdown diff --git a/AGENTS.md b/AGENTS.md index 4170ae3df0..32a00eef1c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -193,12 +193,8 @@ Follow `CONTRIBUTING.md` and `create-github-pr`. Authors without target-reposito ## Go SDK (`sdk/go/`) -- The Go SDK lives in `sdk/go/` with module path `github.com/NVIDIA/OpenShell/sdk/go`. -- Run `mise run go:ci` for the full SDK CI pipeline (lint, build, test, proto-check, docs-check). -- Proto bindings are generated with `mise run go:proto:gen` from the `.proto` files in `proto/`. -- Domain types in `sdk/go/openshell/v1/types/` must not import proto packages. -- Converters in `sdk/go/openshell/v1/internal/converter/` deep-copy slices and maps at boundaries. -- Tests use bufconn for in-process gRPC and testify for assertions. +- The Go SDK moved to [ai-openshell/sdk-go](https://github.com/ai-openshell/sdk-go) (module `github.com/ai-openshell/sdk-go`). Open SDK changes there. +- `sdk/go/` is frozen until it is removed; Branch Checks fails PRs that change it. Proto changes do not need regenerated Go bindings. ## TypeScript SDK (`sdk/typescript/`) diff --git a/sdk/go/README.md b/sdk/go/README.md index dceabfadc7..ec03b0dd13 100644 --- a/sdk/go/README.md +++ b/sdk/go/README.md @@ -1,351 +1,18 @@ -# OpenShell SDK for Go - -[![Go Reference](https://pkg.go.dev/badge/github.com/NVIDIA/OpenShell/sdk/go.svg)](https://pkg.go.dev/github.com/NVIDIA/OpenShell/sdk/go) -[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](../../LICENSE) +# OpenShell SDK for Go (moved) > [!IMPORTANT] -> **[Read the full documentation](https://ro14nd.de/openshell-sdk-go/)** for guides, API reference with gRPC mapping, and testing patterns. - -A Go SDK for interacting with [OpenShell](https://github.com/NVIDIA/OpenShell) -servers, providing idiomatic Go bindings for shell session management, command -execution, provider configuration, and service exposure. - -## Why a Go SDK? - -Go is the language of the Kubernetes ecosystem. If you want to build an -operator, controller, or any automation that manages OpenShell resources as -native Kubernetes objects, you need a Go client. - -This SDK is modeled after -[`k8s.io/client-go`](https://github.com/kubernetes/client-go), the standard -Kubernetes client library that every Go operator developer already knows. The -patterns will look familiar: - -- **Typed sub-clients per resource**: `client.Sandboxes()`, `client.Providers()`, - `client.Exec()`, just like `clientset.CoreV1().Pods()` -- **Domain types separated from wire formats**: clean Go structs in a `types` - package, no proto leakage into the public API (like `k8s.io/api`) -- **Watch primitives**: channel-based watchers with `ResultChan()` and `Stop()`, - identical to `watch.Interface` in client-go -- **Functional options**: variadic option patterns for list filtering, - pagination, and watch configuration. Nil options are silently ignored - at every entry point, so conditional option lists are safe to pass - without filtering out nil entries. -- **Composable auth with token refresh**: wraps `oauth2.TokenSource` for - automatic token caching and coalesced refresh, following the k8s client-go - `cachingTokenSource` pattern -- **Fake client for testing**: an in-memory implementation of the full client - interface (like `k8s.io/client-go/kubernetes/fake`), so operators can be tested - without a real gateway - -## Quick Start - -```go -import v1 "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1" - -// Connect to a gateway -client, err := v1.NewClient(v1.Config{ - Address: "gateway.example.com:443", - Auth: v1.StaticToken("my-token"), -}) -if err != nil { - log.Fatal(err) -} -defer client.Close() - -// Create a sandbox and wait until it's ready -sandbox, err := client.Sandboxes().Create(ctx, "default", "my-sandbox", &v1.SandboxSpec{ - Template: &v1.SandboxTemplate{Image: "python:3.12"}, -}) -if err != nil { - log.Fatal(err) -} -sandbox, err = client.Sandboxes().WaitReady(ctx, "default", sandbox.Name) -if err != nil { - log.Fatal(err) -} - -// Run a command -result, err := client.Exec().Run(ctx, "default", sandbox.Name, - []string{"python3", "-c", "print('hello from sandbox')"}, - v1.ExecOptions{}, -) -if err != nil { - log.Fatal(err) -} -fmt.Println(string(result.Stdout)) -``` - -### Pagination - -List methods return a lazy pager without issuing a request. `NextPage` fetches -one page with the supplied context, while `ListAll` explicitly exhausts every -page. `PageSize` is a per-request maximum and `PageToken` resumes a prior query. - -```go -pager, err := client.Sandboxes().List("default", v1.ListOptions{PageSize: 100}) -if err != nil { - log.Fatal(err) -} -for { - page, err := pager.NextPage(ctx) - if err != nil { - log.Fatal(err) - } - if page == nil { - break - } - for _, sandbox := range page.Items { - fmt.Println(sandbox.Name) - } -} -``` - -### With automatic token refresh - -For OIDC gateways, use `RefreshableToken` to wrap any `oauth2.TokenSource` with -automatic caching and coalesced refresh: +> The Go SDK moved to **[ai-openshell/sdk-go](https://github.com/ai-openshell/sdk-go)**. +> The module path is now `github.com/ai-openshell/sdk-go`. -```go -import "golang.org/x/oauth2" +This directory is frozen and will be removed from NVIDIA/OpenShell. Its last tagged version, `github.com/NVIDIA/OpenShell/sdk/go`, is marked deprecated and gets no further changes. Open issues and pull requests for the SDK in [ai-openshell/sdk-go](https://github.com/ai-openshell/sdk-go). -tokenSource := oauth2Config.TokenSource(ctx, initialToken) -auth, err := v1.RefreshableToken(tokenSource, - v1.WithLeeway(30*time.Second), -) -if err != nil { - log.Fatal(err) -} -client, err := v1.NewClient(v1.Config{ - Address: "gateway.example.com:443", - Auth: auth, -}) -if err != nil { - log.Fatal(err) -} -defer client.Close() -``` - -Concurrent callers share a single refresh call. If the token source fails, the -SDK falls back to the cached token with a logged warning until that token -expires; after that, RPCs fail with the refresh error. See the -[Auth](https://ro14nd.de/openshell-sdk-go/api/auth.html) docs for details. - -### With edge proxy headers - -When a gateway sits behind a zero-trust reverse proxy, use `WithExtraHeaders` to -attach proxy-specific headers alongside standard auth: - -```go -base := v1.StaticToken("my-gateway-token") -auth, err := v1.WithExtraHeaders(base, map[string]string{ - "x-proxy-auth": "proxy-secret", -}) -if err != nil { - log.Fatal(err) -} -client, err := v1.NewClient(v1.Config{ - Address: "gateway.example.com:443", - Auth: auth, -}) -``` - -For Cloudflare Access, use the convenience constructor in the `edge` package: - -```go -import "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/edge" - -auth, err := edge.CloudflareAccess(base, os.Getenv("CF_ACCESS_TOKEN")) -``` - -For gRPC behind edge proxies that reject HTTP/2, use the WebSocket tunnel: +## Migrating -```go -tunnel, err := edge.NewTunnelProxy( - "wss://gateway.example.com/ws", - os.Getenv("CF_ACCESS_TOKEN"), -) -if err != nil { - log.Fatal(err) -} -defer tunnel.Close() +Packages and APIs are unchanged; only the module path is new. Rewrite the imports, then fetch the new module: -client, err := v1.NewClient(v1.Config{ - Address: tunnel.Addr(), - Auth: v1.StaticToken("my-token"), - TLS: &v1.TLSConfig{Insecure: true}, // local tunnel, no TLS -}) +```shell +grep -rl --include='*.go' --include=go.mod 'github.com/NVIDIA/OpenShell/sdk/go' . | xargs perl -pi -e 's#github.com/NVIDIA/OpenShell/sdk/go#github.com/ai-openshell/sdk-go#g' +go get github.com/ai-openshell/sdk-go@latest && go mod tidy ``` -### OIDC Login - -The `oidc` package provides gateway-aware OIDC authentication with browser, -keyboard, device code, and client credentials flows: - -```go -import "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/oidc" - -// Gateway-aware login: reads OIDC config from gateway metadata -token, err := oidc.Login(ctx, "my-gateway") -if err != nil { - log.Fatal(err) -} - -// Use the token with the SDK client -client, err := v1.NewClient(v1.Config{ - Address: "gateway.example.com:443", - Auth: v1.StaticToken(token.AccessToken), -}) -``` - -For headless environments, use the device code flow: - -```go -token, err := oidc.DeviceLogin(ctx, - oidc.WithIssuer("https://auth.example.com"), - oidc.WithClientID("my-app"), -) -``` - -For a one-shot service-account token exchange, use `ClientCredentials`. For a -long-running SDK client, attach the renewable, memory-only auth provider: - -```go -auth, err := oidc.NewClientCredentialsAuth( - oidc.WithGateway("my-gateway"), - oidc.WithClientSecretProvider(func(context.Context) (string, error) { - return os.Getenv("OPENSHELL_OIDC_CLIENT_SECRET"), nil - }), -) -if err != nil { - log.Fatal(err) -} - -client, err := v1.NewClient(v1.Config{ - Address: "gateway.example.com:443", - Auth: auth, - TLS: tlsConfig, -}) -if err != nil { - log.Fatal(err) -} -defer client.Close() -``` - -You can use explicit `WithIssuer`, `WithClientID`, `WithScopes`, and -`WithAudience` options instead of `WithGateway`. The provider repeats the grant -before expiry and never writes the secret or access token to disk. - -See the [oidc package docs](https://pkg.go.dev/github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/oidc) for all options and flows. - -See the [Getting Started](https://ro14nd.de/openshell-sdk-go/getting-started.html) guide for the full walkthrough. - -## Migrating from v0.0.101 - -The pre-1.0 SDK intentionally includes source-incompatible API corrections: - -- `TCP.Listen` returns a `ForwardListener` lifecycle handle. The SDK owns the - accept loop; callers dial `Addr()` and call `Close()` instead of calling - `Accept()` or passing the handle to `http.Serve`. -- Resource operations take an explicit workspace, and workspace-bearing domain - types preserve that scope. -- Several public struct field orders changed. Use keyed struct literals. -- Initialisms use Go spelling, including `JSONRPCMaxBodyBytes`. -- Provider profile durations use the exact `RefreshBefore`, `MaxLifetime`, and - `CacheTTL` fields. The legacy whole-second fields were removed. - -These changes are intentional while the module remains below v1. Update callers -as one migration rather than relying on the v0.0.101 API shape. - -## Architecture - -``` -Client - ├── Sandboxes() → SandboxInterface (create, get, list, delete, watch, wait, logs) - ├── Exec() → ExecInterface (run, stream, interactive) - ├── Files() → FileInterface (upload, download) - ├── Health() → HealthInterface (health check, gateway info, current user) - ├── Services() → ServiceInterface (expose, get, list, delete) - ├── Providers() → ProviderInterface (CRUD + ensure) - │ ├── Profiles() → ProfileInterface (list, get, import, update, lint, delete) - │ └── Refresh() → RefreshInterface (configure, status, rotate, delete) - ├── Workspaces() → WorkspaceInterface (create, get, list, delete, members) - └── Policy() → PolicyInterface (draft review, approve, reject, merge, status) -``` - -All domain types live in `openshell/v1/types/`. Proto-to-Go conversions happen in -an internal converter layer. The public API surface uses type aliases so -consumers import a single package. See the [Architecture](https://ro14nd.de/openshell-sdk-go/architecture.html) overview for details. - -## Features - -Use `CloseInteractiveInput(session)` to close stdin and resize input while keeping -output readable. SDK sessions implement the optional `InteractiveSessionControl` -interface (`CloseWrite()` and `Cancel()`); the original `InteractiveSession` -interface remains unchanged for existing mocks and wrappers. Input closure returns -`ErrorUnimplemented` for sessions without that capability and leaves them open. -`CancelInteractive(session)` uses `Cancel()` when available and otherwise calls -`Close()`. SDK close/cancel operations are idempotent; writes and resizes after -input closure return `io.ErrClosedPipe`. Drain `Read` concurrently with waiting for `ExitCode()`. -`ExitCode()` waits for final gRPC status and returns any observed process exit code -alongside a later stream error. An exit event alone does not establish successful -stream completion. - -| Feature | Interface | Docs | -|---------|-----------|------| -| Sandbox lifecycle (create, get, list, delete, watch, wait) | `SandboxInterface` | [Sandboxes](https://ro14nd.de/openshell-sdk-go/api/sandboxes.html) | -| Command execution (collected, streamed, interactive PTY) | `ExecInterface` | [Exec](https://ro14nd.de/openshell-sdk-go/api/exec.html) | -| Provider management (CRUD + idempotent ensure) | `ProviderInterface` | [Providers](https://ro14nd.de/openshell-sdk-go/api/providers.html) | -| Provider profiles (list, import, lint, update) | `ProfileInterface` | [Profiles](https://ro14nd.de/openshell-sdk-go/api/profiles.html) | -| Credential refresh (configure, rotate, status) | `RefreshInterface` | [Refresh](https://ro14nd.de/openshell-sdk-go/api/refresh.html) | -| Service exposure (expose, list, delete) | `ServiceInterface` | [Services](https://ro14nd.de/openshell-sdk-go/api/services.html) | -| File transfer API (transport capability-gated) | `FileInterface` | [Files](https://ro14nd.de/openshell-sdk-go/api/files.html) | -| Policy management (draft review, approve, reject, merge, global policy) | `PolicyInterface` | [Policy](https://ro14nd.de/openshell-sdk-go/api/policy.html) | -| Sandbox logs (streaming retrieval) | `SandboxInterface` | [Sandboxes](https://ro14nd.de/openshell-sdk-go/api/sandboxes.html) | -| Workspace management (create, get, list, delete, members) | `WorkspaceInterface` | [Workspaces](https://ro14nd.de/openshell-sdk-go/api/workspaces.html) | -| Sandbox provider attachment (attach, detach, list) | `SandboxInterface` | [Sandboxes](https://ro14nd.de/openshell-sdk-go/api/sandboxes.html) | -| Gateway info and current user identity | `HealthInterface` | [Health](https://ro14nd.de/openshell-sdk-go/api/health.html) | -| Health checking | `HealthInterface` | [Health](https://ro14nd.de/openshell-sdk-go/api/health.html) | -| SSH tunneling and TCP forwarding | `SSHInterface`, `TCPInterface` | [SSH](https://ro14nd.de/openshell-sdk-go/api/ssh.html), [TCP](https://ro14nd.de/openshell-sdk-go/api/tcp.html) | -| Auth: static token, refreshable token (oauth2.TokenSource) | `AuthProvider` | [Auth](https://ro14nd.de/openshell-sdk-go/api/auth.html) | -| Edge auth: extra headers, Cloudflare Access, WebSocket tunnel | `AuthProvider`, `edge.TunnelProxy` | [Edge](https://ro14nd.de/openshell-sdk-go/api/edge.html) | -| Typed errors (`IsNotFound`, `IsAlreadyExists`, `IsConflict`, ...) | `StatusError` | [Error Handling](https://ro14nd.de/openshell-sdk-go/error-handling.html) | -| Real-time watch with auto-stop on terminal phase | `WatchInterface[T]` | [Sandboxes](https://ro14nd.de/openshell-sdk-go/api/sandboxes.html) | -| Fake client for testing (no gRPC server needed) | `fake.Client` | [Testing](https://ro14nd.de/openshell-sdk-go/testing.html) | -| OIDC login and renewable service auth | `oidc.Login`, `oidc.DeviceLogin`, `oidc.ClientCredentials`, `oidc.NewClientCredentialsAuth` | [OIDC](https://pkg.go.dev/github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/oidc) | -| Gateway config convenience (load CLI gateway configs, auto-wire auth) | `gateway.NewClient`, `gateway.LoadConfig` | [Gateway](https://ro14nd.de/openshell-sdk-go/api/gateway.html) | - -## Prerequisites - -- Go 1.25.13 or later -- [mise](https://mise.jdx.dev) (recommended for reproducible builds) - -## Build and Test - -```bash -git clone https://github.com/NVIDIA/OpenShell.git -cd OpenShell/sdk/go - -mise run test # Run tests with coverage -mise run lint # Run golangci-lint -mise run ci # Full CI pipeline (lint + build + test) -``` - -Build commands use [mise](https://mise.jdx.dev) for reproducible tool management. - -## Documentation - -Full API documentation is available at the [OpenShell Go SDK Docs](https://ro14nd.de/openshell-sdk-go/) site. - -To build the docs locally: - -```bash -cargo install mdbook -mdbook serve docs -``` - -## License - -Apache-2.0. See [LICENSE](../../LICENSE) for details. - -Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SDK releases follow core releases, so SDK `vX.Y.Z` works with core `vX.Y.Z`. The move is tracked in [#5031](https://github.com/NVIDIA/OpenShell/issues/5031). diff --git a/sdk/go/go.mod b/sdk/go/go.mod index e5d3f921ef..8babaad37f 100644 --- a/sdk/go/go.mod +++ b/sdk/go/go.mod @@ -1,3 +1,4 @@ +// Deprecated: this module moved to github.com/ai-openshell/sdk-go. module github.com/NVIDIA/OpenShell/sdk/go go 1.25.13 diff --git a/tasks/ci.toml b/tasks/ci.toml index 7f896c570d..298fc1f7df 100644 --- a/tasks/ci.toml +++ b/tasks/ci.toml @@ -68,7 +68,7 @@ hide = true [ci] description = "Run full checks (lint, compile/type checks, and tests)" -depends = ["lint", "check", "test", "go:ci", "rust:deny:policy"] +depends = ["lint", "check", "test", "rust:deny:policy"] [all] description = "Alias for ci"