-
Notifications
You must be signed in to change notification settings - Fork 0
feat: close proto-to-SDK type gaps for full parity #57
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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e71589a
docs: add edge and fake package API documentation
rhuss 6d54bf0
feat: close proto gaps for profile, credential, and refresh types
rhuss f1b403e
feat: add policy provenance and sandbox template resource fields
rhuss 26e9374
feat: add CreateOptions annotations and ConfigUpdate annotations
rhuss 3834fd4
fix: address review findings from cc-review and bot comments
rhuss 97464d5
test: improve coverage for fake sandbox deep-copy and annotations
rhuss 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
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,91 @@ | ||
| # Edge | ||
|
|
||
| Package: `openshell/v1/edge` | ||
|
|
||
| The edge package provides utilities for connecting to OpenShell gateways | ||
| through edge proxies such as Cloudflare Access. It includes auth wrappers | ||
| for edge proxy headers and a WebSocket tunnel proxy for gRPC transport | ||
| through HTTP/1.1-only proxies. | ||
|
|
||
| ## Cloudflare Access | ||
|
|
||
| Wrap any `AuthProvider` with Cloudflare Access headers | ||
| (`cf-access-jwt-assertion` and `CF_Authorization` cookie): | ||
|
|
||
| ```go | ||
| import "github.com/rhuss/openshell-sdk-go/openshell/v1/edge" | ||
|
|
||
| base := v1.StaticToken("my-gateway-token") | ||
| auth, err := edge.CloudflareAccess(base, os.Getenv("CF_ACCESS_TOKEN")) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| client, err := v1.NewClient(v1.Config{ | ||
| Address: "gateway.example.com:443", | ||
| Auth: auth, | ||
| }) | ||
| ``` | ||
|
|
||
| CloudflareAccess composes with any auth provider, including `RefreshableToken` | ||
| for automatic token refresh: | ||
|
|
||
| ```go | ||
| tokenSource := oauth2Config.TokenSource(ctx, initialToken) | ||
| refreshAuth, err := v1.RefreshableToken(tokenSource) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| auth, err := edge.CloudflareAccess(refreshAuth, cfToken) | ||
| ``` | ||
|
|
||
| ## WebSocket Tunnel | ||
|
|
||
| `TunnelProxy` bridges gRPC connections over a WebSocket tunnel for edge | ||
| proxies that reject standard HTTP/2 POST requests. The tunnel carries | ||
| its own edge token for proxy authentication, independent of the | ||
| application-level auth provider. | ||
|
|
||
| ```go | ||
| tunnel, err := edge.NewTunnelProxy( | ||
| "wss://gateway.example.com/ws", | ||
| os.Getenv("CF_ACCESS_TOKEN"), | ||
| ) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| defer tunnel.Close() | ||
|
|
||
| auth := v1.StaticToken("my-gateway-token") | ||
| client, err := v1.NewClient(v1.Config{ | ||
| Address: tunnel.Addr(), | ||
| Auth: auth, | ||
| TLS: &v1.TLSConfig{Insecure: true}, // local tunnel | ||
| }) | ||
| ``` | ||
|
|
||
| ## Functions | ||
|
|
||
| | Function | Description | | ||
| |----------|-------------| | ||
| | `CloudflareAccess(base, edgeToken)` | Wrap an AuthProvider with Cloudflare Access headers | | ||
| | `NewTunnelProxy(url, edgeToken, opts...)` | Create a WebSocket tunnel proxy for gRPC-over-HTTP/1.1 | | ||
|
|
||
| ## TunnelProxy Methods | ||
|
|
||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `Addr()` | Local listener address for gRPC client to dial | | ||
| | `Close()` | Gracefully drain in-flight connections and shut down | | ||
|
|
||
| ## TunnelOption | ||
|
|
||
| | Constructor | Effect | | ||
| |-------------|--------| | ||
| | `WithTunnelTLS(cfg)` | Configure TLS for the WebSocket connection | | ||
| | `WithTunnelLogger(l)` | Set a logger for tunnel events | | ||
| | `WithCloseTimeout(d)` | Override the graceful shutdown timeout (default 5s) | | ||
|
|
||
| ## Thread Safety | ||
|
|
||
| All exported functions and methods are safe for concurrent use. | ||
| `Close` is idempotent and safe to call multiple times. |
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,112 @@ | ||
| # Fake | ||
|
|
||
| Package: `openshell/v1/fake` | ||
|
|
||
| The fake package provides an in-memory fake implementation of all SDK | ||
| client interfaces for use in consumer test suites. It follows the | ||
| `client-go/kubernetes/fake` pattern: in-memory stores, watch event | ||
| broadcasting, and matching `StatusError` codes for equivalent error | ||
| conditions (`NotFound`, `AlreadyExists`, `Unavailable`, `Unimplemented`). | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ```go | ||
| import "github.com/rhuss/openshell-sdk-go/openshell/v1/fake" | ||
|
|
||
| func TestSandboxLifecycle(t *testing.T) { | ||
| client := fake.NewClient() | ||
| defer client.Close() | ||
|
|
||
| ctx := context.Background() | ||
|
|
||
| sb, err := client.Sandboxes().Create(ctx, "default", "my-sandbox", &v1.SandboxSpec{}, nil) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, types.SandboxProvisioning, sb.Status.Phase) | ||
|
|
||
| sb, err = client.Sandboxes().WaitReady(ctx, "default", "my-sandbox") | ||
| require.NoError(t, err) | ||
| assert.Equal(t, types.SandboxReady, sb.Status.Phase) | ||
|
|
||
| require.NoError(t, client.Sandboxes().Delete(ctx, "default", "my-sandbox")) | ||
| } | ||
| ``` | ||
|
|
||
| ## Creating a Client | ||
|
|
||
| ```go | ||
| func NewClient(opts ...ClientOption) *Client | ||
| ``` | ||
|
|
||
| Returns a fake client implementing `v1.ClientInterface` with all | ||
| sub-clients wired up. Default health result is healthy. Use options | ||
| to customize initial state: | ||
|
|
||
| ```go | ||
| client := fake.NewClient( | ||
| fake.WithHealthResult(&types.HealthResult{Healthy: false}), | ||
| fake.WithCurrentUser(&types.CurrentUser{Subject: "test-user"}), | ||
| fake.WithGatewayInfo(&types.GatewayInfo{Version: "1.0.0"}), | ||
| ) | ||
| ``` | ||
|
|
||
| ## Pre-populating State | ||
|
|
||
| Seed objects directly into the fake stores for test setup: | ||
|
|
||
| ```go | ||
| client := fake.NewClient() | ||
|
|
||
| client.AddSandbox("default", &types.Sandbox{ | ||
| Name: "pre-existing", | ||
| Status: types.SandboxStatus{Phase: types.SandboxReady}, | ||
| }) | ||
|
|
||
| client.AddProvider("default", &types.Provider{ | ||
| Name: "my-provider", | ||
| Spec: types.ProviderSpec{Type: "docker"}, | ||
| }) | ||
|
|
||
| client.AddWorkspace(&types.Workspace{Name: "staging"}) | ||
| client.AddMember("staging", &types.WorkspaceMember{ | ||
| PrincipalSubject: "subject-123", | ||
| Role: types.WorkspaceRoleAdmin, | ||
| }) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| All `Add*` methods deep-copy their arguments; mutating the input after | ||
| insertion does not affect the stored object. | ||
|
|
||
| ## Sub-Client Coverage | ||
|
|
||
| The fake client implements every interface in `v1.ClientInterface`: | ||
|
|
||
| | Accessor | Interface | Behavior | | ||
| |----------|-----------|----------| | ||
| | `Sandboxes()` | `SandboxInterface` | Full CRUD, Watch, WaitReady | | ||
| | `Providers()` | `ProviderInterface` | Full CRUD, Ensure | | ||
| | `Workspaces()` | `WorkspaceInterface` | Full CRUD, Members | | ||
| | `Health()` | `HealthInterface` | Configurable result | | ||
| | `Inference()` | `InferenceInterface` | Route CRUD | | ||
| | `Policy()` | `PolicyInterface` | List, GetStatus (draft ops return Unimplemented) | | ||
| | `Exec()` | `ExecInterface` | Returns Unimplemented | | ||
| | `Files()` | `FileInterface` | Returns Unimplemented | | ||
| | `Services()` | `ServiceInterface` | Returns Unimplemented | | ||
| | `SSH()` | `SSHInterface` | Input validation, then Unimplemented | | ||
| | `TCP()` | `TCPInterface` | Input validation, then Unimplemented | | ||
| | `Config()` | `ConfigInterface` | Returns Unimplemented | | ||
|
|
||
| ## ClientOption | ||
|
|
||
| | Constructor | Effect | | ||
| |-------------|--------| | ||
| | `WithHealthResult(r)` | Set the health check return value | | ||
| | `WithCurrentUser(u)` | Set the current user return value | | ||
| | `WithGatewayInfo(i)` | Set the gateway info return value | | ||
|
|
||
| ## Thread Safety | ||
|
|
||
| All operations are safe for concurrent use from multiple goroutines. | ||
| `Close` is idempotent and causes all subsequent operations to return | ||
| `Unavailable`. | ||
|
|
||
| See also: [Testing Guide](../testing.md) | ||
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
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
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
Oops, something went wrong.
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.