Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/branch-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,31 @@ jobs:

- name: Lint
run: mise run markdown:lint

go:
name: Go
needs: pr_metadata
if: needs.pr_metadata.outputs.should_run == 'true'
runs-on: linux-amd64-cpu8
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Install tools
run: mise install --locked

- name: Lint
run: mise run go:lint

- name: Build
run: mise run go:build

- name: Test
run: mise run go:test

- name: Proto check
run: mise run go:proto:check
14 changes: 14 additions & 0 deletions docs/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ navigation:
title: "Observability"
- folder: kubernetes
title: "Kubernetes"
- section: "SDKs"
slug: sdks
contents:
- section: "Go SDK"
slug: go
contents:
- page: "Getting Started"
path: sdks/go/getting-started.mdx
- page: "Architecture"
path: sdks/go/architecture.mdx
- page: "Error Handling"
path: sdks/go/error-handling.mdx
- page: "Authentication"
path: sdks/go/authentication.mdx
- folder: reference
title: "Reference"
- folder: security
Expand Down
105 changes: 105 additions & 0 deletions docs/sdks/go/architecture.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
title: "Architecture"
description: "Module structure, gRPC transport layer, and proto isolation pattern in the Go SDK."
keywords: "Go SDK, Architecture, gRPC, Protobuf, Module Structure, Transport"
---

The Go SDK follows a layered architecture that keeps protocol details
internal while exposing idiomatic Go types to consumers.

## Module Structure

```
sdk/go/
├── openshell/v1/ # Public API surface
│ ├── client.go # Top-level Client with sub-clients
│ ├── gateway/ # Gateway client factory (reads CLI config)
│ ├── edge/ # Edge API client
│ ├── fake/ # In-memory fakes for testing
│ ├── types/ # Domain types (Config, errors, options)
│ ├── oidc/ # OIDC authentication
│ └── internal/ # Internal helpers (not importable)
│ ├── converter/ # Proto-to-SDK type conversion
│ └── grpc/ # gRPC connection management
└── proto/ # Proto definitions + generated code
├── openshellv1/ # Generated .pb.go files
├── datamodelv1/
└── sandboxv1/
```

## Transport Layer

The SDK communicates with the OpenShell gateway over gRPC. The transport
is fully internal, and consumers interact only with Go types.

```
Consumer Code
openshell/v1.Client ← Public API (Go types)
internal/converter ← Proto ↔ SDK type conversion
internal/grpc ← Connection management, TLS, auth
gRPC transport ← Wire protocol
```

## Proto Isolation

Generated protobuf types live in `proto/` packages and are never exposed
through the public API. The `internal/converter` package handles all
translation between proto messages and SDK domain types.

This means:

- Consumers never import `proto/` packages directly.
- Proto schema changes do not break the public API.
- Deep copies happen at the boundary, so returned values are safe to mutate.

## Sub-Clients

The top-level `Client` provides access to domain-specific sub-clients:

| Method | Returns | Purpose |
|--------|---------|---------|
| `Sandboxes()` | `SandboxInterface` | Create, list, get, delete sandboxes |
| `Providers()` | `ProviderInterface` | List and inspect compute providers |
| `Services()` | `ServiceInterface` | Manage sandbox services |
| `Exec()` | `ExecInterface` | Execute commands in sandboxes |
| `Files()` | `FileInterface` | Upload and download files |
| `Health()` | `HealthInterface` | Gateway health checks |
| `SSH()` | `SSHInterface` | SSH tunnel management |
| `TCP()` | `TCPInterface` | TCP port forwarding |
| `Config()` | `ConfigInterface` | Sandbox configuration management |
| `Policy()` | `PolicyInterface` | Network policy management |

## Client Construction

There are two ways to create a client:

**From gateway configuration** (reads `~/.config/openshell/`):

```go
client, err := gateway.NewClient("my-gateway")
```

**From explicit configuration**:

```go
client, err := v1.NewClient(types.Config{
Address: "localhost:8080",
Auth: myAuthProvider,
})
```

## Concurrency

All client methods are safe for concurrent use. The underlying gRPC
connection handles multiplexing. Call `client.Close()` when done to
release resources.
97 changes: 97 additions & 0 deletions docs/sdks/go/authentication.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
title: "Authentication"
description: "OIDC authentication, token refresh, and gateway auth configuration for the Go SDK."
keywords: "Go SDK, Authentication, OIDC, Token Refresh, TLS, Security"
---

The Go SDK supports multiple authentication modes depending on how the
gateway is configured. The gateway client reads auth settings from the
CLI configuration automatically.

## Authentication Modes

| Mode | When to Use | Configuration |
|------|-------------|---------------|
| **OIDC** | Production gateways with identity provider | `openshell gateway auth` configures tokens |
| **API Key** | Simple deployments, development | Set via gateway config or `WithAuth` option |
| **None** | Local development, insecure gateways | Default when no auth is configured |

## Automatic Auth (Recommended)

When using `gateway.NewClient()`, authentication is resolved automatically
from the CLI configuration:

```go
// Auth is read from ~/.config/openshell/<gateway>/config.yaml
client, err := gateway.NewClient("my-gateway")
```

The CLI stores OIDC tokens after `openshell gateway auth`. The SDK reads
these tokens from disk and wraps them with `RefreshableToken` for
automatic re-reading when the token file is updated.

## Token Refresh

You can wrap any auth provider with `NewTokenRefresher` to add
token-expiry-aware refresh logic with configurable leeway:

```go
import v1 "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1"

refresher := v1.NewTokenRefresher(baseAuth,
v1.WithLeeway(30 * time.Second),
v1.WithLogger(myLogger),
)

client, err := gateway.NewClient("my-gateway",
gateway.WithAuth(refresher),
)
```

## Custom Auth Provider

For programmatic authentication, implement the `AuthProvider` interface
or use the built-in providers:

```go
// Static API key
client, err := gateway.NewClient("my-gateway",
gateway.WithAuth(v1.StaticToken("my-api-key")),
)
```

The `AuthProvider` interface provides gRPC per-RPC credentials. Each
call attaches the token to the request metadata automatically.

## TLS Configuration

Control TLS settings when connecting to gateways:

```go
client, err := gateway.NewClient("my-gateway",
gateway.WithTLS(&types.TLSConfig{
Insecure: false,
CAFile: "/path/to/ca.crt",
}),
)
```

For local development with self-signed certificates, you can skip
verification, but this should never be used in production.

## Testing with Fakes

The fake client does not require authentication. Use it in tests to
avoid gateway dependencies:

```go
import "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/fake"

func TestMyFeature(t *testing.T) {
client := fake.NewClient()
// Use client.Sandboxes(), client.Exec(), etc.
// No gateway connection or auth needed.
}
```
96 changes: 96 additions & 0 deletions docs/sdks/go/error-handling.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
title: "Error Handling"
description: "SDK error types, gRPC status code mapping, and retry patterns."
keywords: "Go SDK, Errors, gRPC, Status Codes, Retry, Error Handling"
---

The SDK translates gRPC status codes into typed Go errors. All errors
returned by client methods can be inspected with `errors.As` to extract
structured details.

## StatusError

The primary error type is `types.StatusError`:

```go
import "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types"

sandbox, err := client.Sandboxes().Get(ctx, "missing")
if err != nil {
var se *types.StatusError
if errors.As(err, &se) {
fmt.Printf("Code: %d, Message: %s\n", se.Code, se.Message)
}
}
```

## Error Codes

The SDK defines error codes that map to gRPC status codes:

| SDK Error Code | gRPC Status | Meaning |
|----------------|-------------|---------|
| `ErrorNotFound` | `NOT_FOUND` | Resource does not exist |
| `ErrorAlreadyExists` | `ALREADY_EXISTS` | Resource name conflict |
| `ErrorInvalidArgument` | `INVALID_ARGUMENT` | Bad request parameters |
| `ErrorPermissionDenied` | `PERMISSION_DENIED` | Insufficient credentials |
| `ErrorUnavailable` | `UNAVAILABLE` | Gateway is unreachable |
| `ErrorDeadlineExceeded` | `DEADLINE_EXCEEDED` | Request timed out |
| `ErrorCancelled` | `CANCELLED` | Request was cancelled |
| `ErrorInternal` | `INTERNAL` | Server-side failure |
| `ErrorUnimplemented` | `UNIMPLEMENTED` | Operation not supported |
| `ErrorConflict` | `ABORTED` / `FAILED_PRECONDITION` | Conflicting operation |

## Checking Error Types

Use helper functions or direct comparison:

```go
if err != nil {
var se *types.StatusError
if errors.As(err, &se) {
switch se.Code {
case types.ErrorNotFound:
// Handle missing resource
case types.ErrorUnavailable:
// Retry or fail over
default:
// Log and return
}
}
}
```

## Retry Configuration

Configure automatic retries for transient failures:

```go
client, err := gateway.NewClient("my-gateway",
gateway.WithRetryPolicy(&types.RetryPolicy{
MaxRetries: 3,
InitialWait: 100 * time.Millisecond,
}),
)
```

The retry policy applies to `Unavailable` and `DeadlineExceeded` errors.
Other error codes are returned immediately without retrying.

## Context Cancellation

All client methods accept a `context.Context`. Cancelled or timed-out
contexts produce standard Go `context.Canceled` or
`context.DeadlineExceeded` errors:

```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

sandboxes, err := client.Sandboxes().List(ctx)
if errors.Is(err, context.DeadlineExceeded) {
// Request timed out
}
```
Loading
Loading