Files
OpenShell/sdk/go/openshell/v1/client.go
Roland Huss 5add9df2cd feat(sdk/go)!: replace positional labels with functional Create options (#3198)
* feat(sdk/go)!: replace positional labels with functional Create options

Move labels and annotations from separate parameter positions into
functional options (WithLabels, WithAnnotations) on Create() and
CreateFromTemplate().

Before: Create(ctx, ws, name, spec, labels, CreateOptions{Annotations: ann})
After:  Create(ctx, ws, name, spec, WithLabels(labels), WithAnnotations(ann))

This unifies two resource metadata fields that were inconsistently
placed (labels as positional, annotations in a struct) into a single
extensible options pattern matching the existing LogOption convention.

BREAKING CHANGE: Create() and CreateFromTemplate() signatures changed.
All callers must use WithLabels() and WithAnnotations() functional
options instead of positional labels and CreateOptions struct.

Closes #2807

Signed-off-by: Roland Huß <rhuss@redhat.com>

* fix(sdk/go): ignore nil Create options

Callers of the previous signature passed nil for the positional labels
parameter to create an unlabeled sandbox:

    Create(ctx, ws, name, spec, nil)

That call still compiles against the new variadic signature, because an
untyped nil converts to CreateOption. ApplyCreateOptions then invoked it
and panicked with a nil pointer dereference, so the migration was unsafe
for existing consumers.

Skip nil options in ApplyCreateOptions and cover the legacy trailing-nil
call with regression tests against ApplyCreateOptions directly, the fake
client (Create and CreateFromTemplate), and the gRPC client.

Signed-off-by: Roland Huß <rhuss@redhat.com>

* refactor(sdk/go): apply Create options through the shared options helper

Signed-off-by: Roland Huß <rhuss@redhat.com>

* docs(sdk/go): use WithLabels in CreateSandboxFromTemplate examples

Signed-off-by: Roland Huß <rhuss@redhat.com>

---------

Signed-off-by: Roland Huß <rhuss@redhat.com>
2026-10-16 07:23:42 +00:00

166 lines
4.9 KiB
Go

// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package v1
import (
"context"
"sync"
"github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types"
internalgrpc "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/internal/grpc"
"google.golang.org/grpc"
)
// Config holds all settings needed to create a Client.
type Config = types.Config
// ClientInterface defines the top-level SDK surface.
type ClientInterface interface {
Sandboxes() SandboxInterface
SandboxTemplates() SandboxTemplateInterface
CreateSandboxFromTemplate(ctx context.Context, workspace, name, templateName string, spec *SandboxSpec, opts ...CreateOption) (*Sandbox, error)
Providers() ProviderInterface
Services() ServiceInterface
Exec() ExecInterface
Files() FileInterface
Health() HealthInterface
SSH() SSHInterface
TCP() TCPInterface
Config() ConfigInterface
Policy() PolicyInterface
Workspaces() WorkspaceInterface
Close() error
}
// SandboxInterface is defined in sandbox.go
// ProviderInterface is defined in provider.go
// ExecInterface is defined in exec.go
// FileInterface is defined in file.go
// Client implements ClientInterface. It holds a gRPC connection and provides
// sub-client accessors following the Kubernetes client-go pattern.
type Client struct {
conn *grpc.ClientConn
config Config
closeOnce sync.Once
closeErr error
sandboxes SandboxInterface
templateCreate SandboxTemplateCreateInterface
templates SandboxTemplateInterface
providers ProviderInterface
services ServiceInterface
exec ExecInterface
files FileInterface
health HealthInterface
ssh SSHInterface
tcp TCPInterface
cfg ConfigInterface
policy PolicyInterface
workspaces WorkspaceInterface
}
// NewClient creates a new SDK client connected to the given gateway.
func NewClient(cfg Config) (*Client, error) {
if cfg.Address == "" {
return nil, &StatusError{Code: ErrorInvalidArgument, Message: "address must not be empty"}
}
if cfg.Auth == nil {
cfg.Auth = NoAuth()
}
var tlsParams *internalgrpc.TLSParams
if cfg.TLS != nil {
tlsParams = &internalgrpc.TLSParams{
CertFile: cfg.TLS.CertFile,
KeyFile: cfg.TLS.KeyFile,
CAFile: cfg.TLS.CAFile,
Insecure: cfg.TLS.Insecure,
}
}
conn, err := internalgrpc.NewConnection(cfg.Address, tlsParams, cfg.Auth)
if err != nil {
return nil, err
}
c := &Client{
conn: conn,
config: cfg,
}
sandboxes := newSandboxClient(conn)
c.sandboxes = sandboxes
c.templateCreate = sandboxes
c.templates = newSandboxTemplateClient(conn)
c.providers = newProviderClient(conn)
c.services = newServiceClient(conn)
c.exec = newExecClient(conn, c.sandboxes)
c.files = newFileClient(conn, c.sandboxes)
c.health = newHealthClient(conn)
c.ssh = newSSHClient(conn, c.sandboxes)
c.tcp = newTCPClient(conn, c.sandboxes, c.ssh)
c.cfg = newConfigClient(conn, c.sandboxes)
c.policy = newPolicyClient(conn)
c.workspaces = newWorkspaceClient(conn)
return c, nil
}
// Sandboxes returns the sandbox sub-client.
func (c *Client) Sandboxes() SandboxInterface { return c.sandboxes }
// SandboxTemplates returns the reusable sandbox template sub-client.
func (c *Client) SandboxTemplates() SandboxTemplateInterface { return c.templates }
// CreateSandboxFromTemplate creates a sandbox from a named workload template
// without changing the legacy Sandboxes() interface.
func (c *Client) CreateSandboxFromTemplate(ctx context.Context, workspace, name, templateName string, spec *SandboxSpec, opts ...CreateOption) (*Sandbox, error) {
return c.templateCreate.CreateFromTemplate(ctx, workspace, name, templateName, spec, opts...)
}
// Providers returns the provider sub-client.
func (c *Client) Providers() ProviderInterface { return c.providers }
// Services returns the service sub-client.
func (c *Client) Services() ServiceInterface { return c.services }
// Exec returns the exec sub-client.
func (c *Client) Exec() ExecInterface { return c.exec }
// Files returns the file sub-client.
func (c *Client) Files() FileInterface { return c.files }
// Health returns the health sub-client.
func (c *Client) Health() HealthInterface { return c.health }
// SSH returns the SSH session sub-client.
func (c *Client) SSH() SSHInterface { return c.ssh }
// TCP returns the TCP port forwarding sub-client.
func (c *Client) TCP() TCPInterface { return c.tcp }
// Config returns the configuration sub-client.
func (c *Client) Config() ConfigInterface { return c.cfg }
// Policy returns the policy management sub-client.
func (c *Client) Policy() PolicyInterface { return c.policy }
// Workspaces returns the workspace management sub-client.
func (c *Client) Workspaces() WorkspaceInterface { return c.workspaces }
// Close closes the underlying gRPC connection. Safe to call multiple times.
func (c *Client) Close() error {
c.closeOnce.Do(func() {
c.closeErr = c.conn.Close()
})
return c.closeErr
}