feat(sdk/go): add Go SDK foundation, types, and sandbox client (A)#2271
feat(sdk/go): add Go SDK foundation, types, and sandbox client (A)#2271rhuss wants to merge 5 commits into
Conversation
Add the Go SDK module with the full API contract and a working sandbox client as the first vertical slice. All other resource clients are present as stubs returning Unimplemented errors, to be replaced with real implementations in subsequent PRs. Contents: - Module setup (go.mod, Makefile, mise.toml) - All domain types (types/ package) - Full ClientInterface with all sub-client accessors - Shared infrastructure (errors, auth, gRPC connection, logging) - Sandbox client with converter and tests (fully functional) - Stub clients for remaining resources (exec, file, health, provider, profile, config, refresh, policy, service, ssh, tcp) Part of the Go SDK decomposition plan (NVIDIA#2270). Implements NVIDIA#2044.
Principal Engineer Review — Go SDK foundation (A)Reviewed by checking out the branch and reading every non-generated file. Blocking1. Dead code will fail the project's own lint gate —
|
- Make scheme parsing drive transport selection: http:// uses plaintext gRPC, https:// or no scheme uses TLS. Add regression tests. - Add Resources and DriverConfig fields to SandboxTemplate and update both converter directions (SandboxFromProto/SandboxSpecToProto). - Regenerate proto bindings from current canonical proto sources to eliminate drift (SigV4/MCP fields, params matchers, reserved fields). - Run gofmt/goimports on all handwritten Go files. Signed-off-by: Roland Huß <rhuss@redhat.com>
russellb
left a comment
There was a problem hiding this comment.
[codex:gpt-5.5] Finding 1: The Go SDK still drops active sandbox policy fields from the handwritten types/converters. The synced proto includes credential_signing, signing_service, signing_region, json_rpc_max_body_bytes, mcp, and params on L7 allow/deny rules, but PolicyNetworkEndpoint, L7Allow, L7DenyRule, and the converters omit them. Since Create sends SandboxSpecToProto, Go clients cannot express current SigV4/MCP/JSON-RPC policy controls, and server-returned policies lose these fields on round-trip. Please add SDK fields and bidirectional converter coverage for every current proto policy field. Refs: sdk/go/openshell/v1/types/network_policy.go:19, sdk/go/openshell/v1/internal/converter/network_policy.go:65, proto/sandbox.proto:131, proto/sandbox.proto:211.
[codex:gpt-5.5] Finding 2: mapToStruct ignores structpb.NewStruct errors for SandboxTemplate.Resources and DriverConfig. Invalid UTF-8 keys or unsupported map[string]any values make NewStruct return nil, err, but the SDK silently sends nil, so user-provided template config can disappear without an error. Please make sandbox spec conversion fallible, validate before CreateSandbox, or expose a safer typed representation, and add tests for invalid values. Refs: sdk/go/openshell/v1/internal/converter/copy.go:60, sdk/go/openshell/v1/internal/converter/sandbox.go:170, sdk/go/openshell/v1/sandbox_client.go:28.
- Remove dead boolCount function that would fail golangci-lint (#1) - Emit EventAdded for the first watch event instead of EventModified, matching k8s watch semantics (#7) - Add mutex locking to all mock server methods that access the shared sandboxes map, fixing latent race conditions (#12) - Skip HealthCheck integration test that calls an unimplemented stub (#13) - Scope doc.go examples: mark sections for sub-clients not yet available in this PR with "available in a future release" (#4) - Document Config.Timeout/RetryPolicy/Logger and WatchOptions fields as reserved for future use (#2, #6) Signed-off-by: Roland Huß <rhuss@redhat.com>
|
My agent's response to #2271 (comment). Most of the things are because of this artificial split to get the PRs down to something more consumable (which was also important as I hight some size limits for code agent's review when I dropped it). But thank you very much for jumping on it, I've addressed the comments (and delayed some until we get the full combo in) Thanks for the review. Here is my assessment, classifying each finding by root cause: Already addressed (in a prior fix commit
Fixed now (commit
Deferred to later PRs (expected from the A-F split):
Accepted as low-priority (not blocking):
|
Sounds good. I figured some of it would be off, but that your agent would sort it out. :) |
There was a problem hiding this comment.
Are these just getting copied from the root protos/?
There was a problem hiding this comment.
Yes, they are copies from the root proto/ directory, with a proto:sync task and UPSTREAM_VERSION file to track provenance.
This is intentional. The copy+sync approach decouples SDK maintenance from core API changes. Without this, any contributor who touches a proto file would also need to fix all in-tree SDK bindings in the same PR. That's a high bar, especially as the number of SDK languages grows. Not everyone who adds a field to a proto can (or should have to) fix the Go, TypeScript, and future Python clients. Instead, a sync workflow detects proto changes and opens issues/PRs so the respective SDK maintainers can handle the adaptation on their own timeline.
Kubernetes uses a similar pattern (staging directories + publishing-bot), and Envoy's go-control-plane syncs protos via GitHub Actions rather than referencing them in-place. The trade-off is duplication vs. independence.
The alternative would be separate repositories per SDK, which is what Kubernetes and Temporal do. But that requires a common GitHub organization we can govern and control. With company-owned orgs, adding new repos typically involves heavy internal processes, so that's not a practical path right now.
That said, I agree we need alignment across SDKs. #2122 (TypeScript SDK by @maxdubrinsky) uses buf which references root protos directly, so we'd have two different approaches. @maxdubrinsky, could you weigh in on whether the TypeScript SDK would also benefit from a decoupled copy+sync model, or whether you see advantages to the buf approach that should inform the Go SDK too? Given how quickly the protos are still evolving, reducing friction for contributors who aren't SDK specialists is a key goal IMO.
There was a problem hiding this comment.
This seems superfluous given our use of mise. Is there a reason to have this?
There was a problem hiding this comment.
The Makefile is intentional as an entry point for Go developers. In the Go ecosystem, mise is essentially unknown, and Makefiles are the standard build tool (most Go projects use make test, make lint, etc.). Since the SDK targets external Go contributors who may never have seen mise, the Makefile picks them up where they are and reduces onboarding friction.
That said, it's a thin convenience layer and I don't feel strongly about it. Happy to drop it if the team prefers mise-only consistency across the repo.
There was a problem hiding this comment.
By convention this would go into something like tasks/sdk/go.toml. Is there a reason to create a new mise root here?
There was a problem hiding this comment.
We should also get some alignment between this PR and #2122, which adds the TypeScript SDK.
There was a problem hiding this comment.
The mise.toml here serves as a self-contained SDK development root, not just a task file. It declares SDK-specific tool dependencies (go, protoc, golangci-lint, protoc-gen-go, protoc-gen-go-grpc) alongside the tasks. A tasks/sdk/go.toml include can only define tasks, not tool versions.
This lets a Go contributor cd sdk/go && mise install and get a complete, isolated development environment without pulling in the root project's Rust/Python/Node toolchain. It follows the same self-containment principle as the proto copy discussed above.
That said, I don't feel as strongly about this as the proto decoupling. Moving tasks to tasks/sdk/go.toml and tool versions into the root mise.toml is a one-time change and not a big burden, so happy to adapt if the team prefers consistency.
On alignment with #2122: agreed, already pinged @maxdubrinsky on the proto thread above.
There was a problem hiding this comment.
THinking again, I will update the build for better consistency.
There was a problem hiding this comment.
Moved to the centralized pattern in acac231: Go tools are now in root mise.toml, tasks in tasks/go.toml with go: namespace and dir = "sdk/go". The standalone sdk/go/mise.toml is removed.
mise tasks --hidden | grep go: confirms all 11 tasks are registered and mise run go:test passes.
| @@ -0,0 +1 @@ | |||
| 7c5b9b92c9e176eddd081ab545c9f9f925c2e213 | |||
There was a problem hiding this comment.
How do we envision the SDK getting developed? Pinning the upstream version seems to break the monorepo convention where all code is intended to work at the tip of main.
To provide a concrete scenario: if i add a new field to a proto and associated services implemention, should the PR update the SDKs for that proto change as well?
There was a problem hiding this comment.
The UPSTREAM_VERSION pin is a leftover from the original standalone repo prototype where this connection was needed. I'm happy to remove it since in a monorepo, git diff between sdk/go/proto/ and proto/ already tells you if the SDK is behind. CI can check this directly.
But the underlying question stands regardless of the version file: should a PR that adds a proto field and updates the service implementation also need to update all SDK bindings? I'd argue no. That's the friction we're trying to avoid.
The deeper motivation is about community handover. In a project with SDKs in Go, TypeScript, Python, and a core in Rust, no single contributor is a domain expert in all of these. I have basic TypeScript knowledge but wouldn't trust myself with idiomatic TypeScript patterns, and I'm still learning Rust. Others may be TypeScript experts who've never written Go. Forcing every proto change through all SDKs means either blocking on specialists or accepting non-idiomatic fixes from generalists. It's better to let people do what they're best at and optimize the handover points.
What we should add is automation to make this handover smooth: a CI job that diffs root protos against each SDK's copies and flags divergence, plus a hard release gate requiring all SDKs to be green before shipping. That way the decoupling doesn't become invisible drift.
I agree the copy approach creates a visible deviation from the "tip of main" monorepo convention. But the convention assumes a single team maintaining all code. Whether we copy protos or reference them in-place is the design decision we need to align on across all SDKs (see the proto thread above with @maxdubrinsky).
There was a problem hiding this comment.
Removed the UPSTREAM_VERSION file and its references in db55f26. It was a leftover from the standalone repo prototype. In a monorepo, git diff sdk/go/proto/ proto/ already shows drift, and CI can enforce this directly.
The proto:sync task now defaults to ../../proto (the root proto directory) and no longer writes a version file.
There was a problem hiding this comment.
We should also get some alignment between this PR and #2122, which adds the TypeScript SDK.
| @@ -0,0 +1,242 @@ | |||
| [tools] | |||
| go = "1.25" | |||
| protoc = "29.6" | |||
There was a problem hiding this comment.
Aligning on buf vs protoc makes sense, and I'm open to switching. But this choice is downstream of the proto management decision we're discussing on the other threads: whether SDKs copy protos locally or reference them from the root.
If we go with copy+sync (my preference, for the decoupling reasons above), either tool works fine for generating from local copies. If we go with buf's workspace model (referencing root protos directly), then buf is the natural fit.
I'd suggest we settle the proto management approach first (already pinged @maxdubrinsky above), then align the generation tooling across both SDKs to match.
Move Go SDK mise configuration from standalone sdk/go/mise.toml into the project's centralized pattern: - Add Go tools (go, golangci-lint, protoc-gen-go, protoc-gen-go-grpc) to root mise.toml [tools] section - Create tasks/go.toml with all SDK tasks using go: namespace prefix and dir=sdk/go for working directory - Update sdk/go/Makefile to reference namespaced task names - Update proto:sync default path for monorepo layout Addresses review feedback from drew on PR NVIDIA#2271 regarding mise convention alignment. Signed-off-by: Roland Huß <rhuss@redhat.com>
Remove sdk/go/proto/UPSTREAM_VERSION file and its exclusion from proto:check. This was a leftover from the standalone repo prototype. In a monorepo, proto drift is detectable via git diff between sdk/go/proto/ and proto/ directly. Signed-off-by: Roland Huß <rhuss@redhat.com>
|
Follow-up: #2344 implements the SDK proto sync CI automation we discussed in the threads above. It adds Depends on this PR (#2271) for the |
Context
This is the first PR in a 6-PR decomposition of the Go SDK contribution (#2044). The decomposition was discussed in the contributor meeting on 2026-07-14 to make the review process more approachable.
The first PR is intentionally the largest because it carries the shared foundation. After this merge, the SDK is usable end-to-end for sandbox management. Each subsequent PR then incrementally adds one more resource group, and after every merge the SDK is fully working with an expanded API surface.
What's in this PR
go.mod,go.sum,Makefile,mise.tomltypes/package (14 files) covering every SDK resourceClientInterface: all 10 sub-client accessors defined upfrontUnimplementederrors linking to feat(sdk/go): Go SDK PR decomposition plan #2270. Each subsequent PR replaces stubs with real implementations.How to Review
Review zones
client.go,types/*.go,errors.go,auth*.go,sandbox.go,sandbox_client.go,internal/grpc/conn.gosandbox_client_test.go,internal/converter/sandbox.go,internal/converter/sandbox_test.gosandbox_client_test.goas the test pattern exemplar. Converter tests follow table-driven patterns.stub_clients.go,go.sum,Makefile,mise.toml,doc.go, interface-only files (exec.go,file.go, etc.)proto/*.pb.go,proto/*_grpc.pb.goKey design decisions
types/package has no proto imports, insulating consumers from wire format changesErrorUnimplementedwith a link to the tracking issue. Each follow-up PR replaces stubs with real implementations without modifyingclient.go.What to look for
ClientInterfacecovers the right API surfaceIsNotFound(), etc.)Testing
All 130 tests pass:
Resolves #2044 (with remaining PRs B-F)
Part of #2270