Skip to content

Commit 55985e2

Browse files
committed
docs(sdk): add Go SDK documentation and CI job
Add Fern MDX documentation pages (getting-started, architecture, error-handling, authentication), wire into docs navigation, create go:proto mise task, and add Go job to branch-checks CI workflow. Part 3 of 4 for issue NVIDIA#2044. Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
1 parent 8ec90d2 commit 55985e2

7 files changed

Lines changed: 464 additions & 0 deletions

File tree

.github/workflows/branch-checks.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,31 @@ jobs:
190190

191191
- name: Lint
192192
run: mise run markdown:lint
193+
194+
go:
195+
name: Go
196+
needs: pr_metadata
197+
if: needs.pr_metadata.outputs.should_run == 'true'
198+
runs-on: linux-amd64-cpu8
199+
container:
200+
image: ghcr.io/nvidia/openshell/ci:latest
201+
credentials:
202+
username: ${{ github.actor }}
203+
password: ${{ secrets.GITHUB_TOKEN }}
204+
steps:
205+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
206+
207+
- name: Install tools
208+
run: mise install --locked
209+
210+
- name: Lint
211+
run: mise run go:lint
212+
213+
- name: Build
214+
run: mise run go:build
215+
216+
- name: Test
217+
run: mise run go:test
218+
219+
- name: Proto check
220+
run: mise run go:proto:check

docs/index.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ navigation:
2323
title: "Observability"
2424
- folder: kubernetes
2525
title: "Kubernetes"
26+
- section: "SDKs"
27+
slug: sdks
28+
contents:
29+
- section: "Go SDK"
30+
slug: go
31+
contents:
32+
- page: "Getting Started"
33+
path: sdks/go/getting-started.mdx
34+
- page: "Architecture"
35+
path: sdks/go/architecture.mdx
36+
- page: "Error Handling"
37+
path: sdks/go/error-handling.mdx
38+
- page: "Authentication"
39+
path: sdks/go/authentication.mdx
2640
- folder: reference
2741
title: "Reference"
2842
- folder: security

docs/sdks/go/architecture.mdx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
title: "Architecture"
5+
description: "Module structure, gRPC transport layer, and proto isolation pattern in the Go SDK."
6+
keywords: "Go SDK, Architecture, gRPC, Protobuf, Module Structure, Transport"
7+
---
8+
9+
The Go SDK follows a layered architecture that keeps protocol details
10+
internal while exposing idiomatic Go types to consumers.
11+
12+
## Module Structure
13+
14+
```
15+
sdk/go/
16+
├── openshell/v1/ # Public API surface
17+
│ ├── client.go # Top-level Client with sub-clients
18+
│ ├── gateway/ # Gateway client factory (reads CLI config)
19+
│ ├── edge/ # Edge API client
20+
│ ├── fake/ # In-memory fakes for testing
21+
│ ├── types/ # Domain types (Config, errors, options)
22+
│ ├── oidc/ # OIDC authentication
23+
│ └── internal/ # Internal helpers (not importable)
24+
│ ├── converter/ # Proto-to-SDK type conversion
25+
│ └── grpc/ # gRPC connection management
26+
└── proto/ # Proto definitions + generated code
27+
├── openshellv1/ # Generated .pb.go files
28+
├── datamodelv1/
29+
└── sandboxv1/
30+
```
31+
32+
## Transport Layer
33+
34+
The SDK communicates with the OpenShell gateway over gRPC. The transport
35+
is fully internal, and consumers interact only with Go types.
36+
37+
```
38+
Consumer Code
39+
40+
41+
openshell/v1.Client ← Public API (Go types)
42+
43+
44+
internal/converter ← Proto ↔ SDK type conversion
45+
46+
47+
internal/grpc ← Connection management, TLS, auth
48+
49+
50+
gRPC transport ← Wire protocol
51+
```
52+
53+
## Proto Isolation
54+
55+
Generated protobuf types live in `proto/` packages and are never exposed
56+
through the public API. The `internal/converter` package handles all
57+
translation between proto messages and SDK domain types.
58+
59+
This means:
60+
61+
- Consumers never import `proto/` packages directly.
62+
- Proto schema changes do not break the public API.
63+
- Deep copies happen at the boundary, so returned values are safe to mutate.
64+
65+
## Sub-Clients
66+
67+
The top-level `Client` provides access to domain-specific sub-clients:
68+
69+
| Method | Returns | Purpose |
70+
|--------|---------|---------|
71+
| `Sandboxes()` | `SandboxInterface` | Create, list, get, delete sandboxes |
72+
| `Providers()` | `ProviderInterface` | List and inspect compute providers |
73+
| `Services()` | `ServiceInterface` | Manage sandbox services |
74+
| `Exec()` | `ExecInterface` | Execute commands in sandboxes |
75+
| `Files()` | `FileInterface` | Upload and download files |
76+
| `Health()` | `HealthInterface` | Gateway health checks |
77+
| `SSH()` | `SSHInterface` | SSH tunnel management |
78+
| `TCP()` | `TCPInterface` | TCP port forwarding |
79+
| `Policy()` | `PolicyInterface` | Network policy management |
80+
81+
## Client Construction
82+
83+
There are two ways to create a client:
84+
85+
**From gateway configuration** (reads `~/.config/openshell/`):
86+
87+
```go
88+
client, err := gateway.NewClient("my-gateway")
89+
```
90+
91+
**From explicit configuration**:
92+
93+
```go
94+
client, err := v1.NewClient(types.Config{
95+
Address: "localhost:8080",
96+
Auth: myAuthProvider,
97+
})
98+
```
99+
100+
## Concurrency
101+
102+
All client methods are safe for concurrent use. The underlying gRPC
103+
connection handles multiplexing. Call `client.Close()` when done to
104+
release resources.

docs/sdks/go/authentication.mdx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
title: "Authentication"
5+
description: "OIDC authentication, token refresh, and gateway auth configuration for the Go SDK."
6+
keywords: "Go SDK, Authentication, OIDC, Token Refresh, TLS, Security"
7+
---
8+
9+
The Go SDK supports multiple authentication modes depending on how the
10+
gateway is configured. The gateway client reads auth settings from the
11+
CLI configuration automatically.
12+
13+
## Authentication Modes
14+
15+
| Mode | When to Use | Configuration |
16+
|------|-------------|---------------|
17+
| **OIDC** | Production gateways with identity provider | `openshell gateway auth` configures tokens |
18+
| **API Key** | Simple deployments, development | Set via gateway config or `WithAuth` option |
19+
| **None** | Local development, insecure gateways | Default when no auth is configured |
20+
21+
## Automatic Auth (Recommended)
22+
23+
When using `gateway.NewClient()`, authentication is resolved automatically
24+
from the CLI configuration:
25+
26+
```go
27+
// Auth is read from ~/.config/openshell/<gateway>/config.yaml
28+
client, err := gateway.NewClient("my-gateway")
29+
```
30+
31+
The CLI stores OIDC tokens after `openshell gateway auth`. The SDK reads
32+
these tokens and refreshes them transparently.
33+
34+
## OIDC Token Refresh
35+
36+
For OIDC-authenticated gateways, the SDK handles token refresh
37+
automatically. You can customize the refresh behavior:
38+
39+
```go
40+
import "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/oidc"
41+
42+
// The refresh wrapper handles token renewal before expiry.
43+
// Configure leeway to refresh tokens before they expire.
44+
refresher := v1.NewTokenRefresher(baseAuth,
45+
v1.WithLeeway(30 * time.Second),
46+
v1.WithLogger(myLogger),
47+
)
48+
49+
client, err := gateway.NewClient("my-gateway",
50+
gateway.WithAuth(refresher),
51+
)
52+
```
53+
54+
## Custom Auth Provider
55+
56+
For programmatic authentication, implement the `AuthProvider` interface
57+
or use the built-in providers:
58+
59+
```go
60+
// Static API key
61+
client, err := gateway.NewClient("my-gateway",
62+
gateway.WithAuth(types.StaticToken("my-api-key")),
63+
)
64+
```
65+
66+
The `AuthProvider` interface provides gRPC per-RPC credentials. Each
67+
call attaches the token to the request metadata automatically.
68+
69+
## TLS Configuration
70+
71+
Control TLS settings when connecting to gateways:
72+
73+
```go
74+
client, err := gateway.NewClient("my-gateway",
75+
gateway.WithTLS(&types.TLSConfig{
76+
InsecureSkipVerify: false,
77+
CACertFile: "/path/to/ca.crt",
78+
}),
79+
)
80+
```
81+
82+
For local development with self-signed certificates, you can skip
83+
verification, but this should never be used in production.
84+
85+
## Testing with Fakes
86+
87+
The fake client does not require authentication. Use it in tests to
88+
avoid gateway dependencies:
89+
90+
```go
91+
import "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/fake"
92+
93+
func TestMyFeature(t *testing.T) {
94+
client := fake.NewClient()
95+
// Use client.Sandboxes(), client.Exec(), etc.
96+
// No gateway connection or auth needed.
97+
}
98+
```

docs/sdks/go/error-handling.mdx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
title: "Error Handling"
5+
description: "SDK error types, gRPC status code mapping, and retry patterns."
6+
keywords: "Go SDK, Errors, gRPC, Status Codes, Retry, Error Handling"
7+
---
8+
9+
The SDK translates gRPC status codes into typed Go errors. All errors
10+
returned by client methods can be inspected with `errors.As` to extract
11+
structured details.
12+
13+
## StatusError
14+
15+
The primary error type is `types.StatusError`:
16+
17+
```go
18+
import "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types"
19+
20+
sandbox, err := client.Sandboxes().Get(ctx, "missing")
21+
if err != nil {
22+
var se *types.StatusError
23+
if errors.As(err, &se) {
24+
fmt.Printf("Code: %d, Message: %s\n", se.Code, se.Message)
25+
}
26+
}
27+
```
28+
29+
## Error Codes
30+
31+
The SDK defines error codes that map to gRPC status codes:
32+
33+
| SDK Error Code | gRPC Status | Meaning |
34+
|----------------|-------------|---------|
35+
| `NotFound` | `NOT_FOUND` | Resource does not exist |
36+
| `AlreadyExists` | `ALREADY_EXISTS` | Resource name conflict |
37+
| `InvalidArgument` | `INVALID_ARGUMENT` | Bad request parameters |
38+
| `PermissionDenied` | `PERMISSION_DENIED` | Insufficient credentials |
39+
| `Unauthenticated` | `UNAUTHENTICATED` | Missing or expired token |
40+
| `Unavailable` | `UNAVAILABLE` | Gateway is unreachable |
41+
| `Internal` | `INTERNAL` | Server-side failure |
42+
43+
## Checking Error Types
44+
45+
Use helper functions or direct comparison:
46+
47+
```go
48+
if err != nil {
49+
var se *types.StatusError
50+
if errors.As(err, &se) {
51+
switch se.Code {
52+
case types.NotFound:
53+
// Handle missing resource
54+
case types.Unavailable:
55+
// Retry or fail over
56+
default:
57+
// Log and return
58+
}
59+
}
60+
}
61+
```
62+
63+
## Retry Configuration
64+
65+
Configure automatic retries for transient failures:
66+
67+
```go
68+
client, err := gateway.NewClient("my-gateway",
69+
gateway.WithRetryPolicy(&types.RetryPolicy{
70+
MaxRetries: 3,
71+
Backoff: 100 * time.Millisecond,
72+
}),
73+
)
74+
```
75+
76+
The retry policy applies to `Unavailable` and `DeadlineExceeded` errors.
77+
Other error codes are returned immediately without retrying.
78+
79+
## Context Cancellation
80+
81+
All client methods accept a `context.Context`. Cancelled or timed-out
82+
contexts produce standard Go `context.Canceled` or
83+
`context.DeadlineExceeded` errors:
84+
85+
```go
86+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
87+
defer cancel()
88+
89+
sandboxes, err := client.Sandboxes().List(ctx)
90+
if errors.Is(err, context.DeadlineExceeded) {
91+
// Request timed out
92+
}
93+
```

0 commit comments

Comments
 (0)