Add Client struct wrapper and update module path for go get
Adds a Client struct that wraps the function-based API for consumers that prefer method receivers (e.g. orchestra). Updates module path to github.com/russellballestrini/un-inception/clients/go/sync/src for public go get resolution.
This commit is contained in:
parent
8d17b2babc
commit
8093386568
2 changed files with 201 additions and 1 deletions
200
clients/go/sync/src/client.go
Normal file
200
clients/go/sync/src/client.go
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
// Client provides a struct-based API around the function-based SDK.
|
||||||
|
// Designed for consumers like orchestra that prefer method receivers
|
||||||
|
// and context-aware patterns.
|
||||||
|
package un
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client wraps resolved credentials for method-based API access.
|
||||||
|
type Client struct {
|
||||||
|
Creds *Credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a client from explicit credentials.
|
||||||
|
func NewClient(publicKey, secretKey string) *Client {
|
||||||
|
return &Client{Creds: &Credentials{PublicKey: publicKey, SecretKey: secretKey}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientFromEnv resolves credentials from the 4-tier priority system.
|
||||||
|
func NewClientFromEnv() (*Client, error) {
|
||||||
|
creds, err := ResolveCredentials("", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Client{Creds: creds}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteResult holds typed execution output.
|
||||||
|
type ExecuteResult struct {
|
||||||
|
JobID string
|
||||||
|
Status string
|
||||||
|
Output string
|
||||||
|
Error string
|
||||||
|
Raw map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toExecuteResult(raw map[string]interface{}) *ExecuteResult {
|
||||||
|
r := &ExecuteResult{Raw: raw}
|
||||||
|
if v, ok := raw["job_id"].(string); ok {
|
||||||
|
r.JobID = v
|
||||||
|
}
|
||||||
|
if v, ok := raw["status"].(string); ok {
|
||||||
|
r.Status = v
|
||||||
|
}
|
||||||
|
// unsandbox API returns stdout/stderr, not output/error
|
||||||
|
if v, ok := raw["output"].(string); ok {
|
||||||
|
r.Output = v
|
||||||
|
}
|
||||||
|
if v, ok := raw["stdout"].(string); ok && r.Output == "" {
|
||||||
|
r.Output = v
|
||||||
|
}
|
||||||
|
if v, ok := raw["error"].(string); ok {
|
||||||
|
r.Error = v
|
||||||
|
}
|
||||||
|
if v, ok := raw["stderr"].(string); ok && r.Error == "" {
|
||||||
|
r.Error = v
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute runs code synchronously (blocks until completion).
|
||||||
|
func (c *Client) Execute(_ context.Context, language, code string) (*ExecuteResult, error) {
|
||||||
|
return c.ExecuteWithOpts(context.Background(), language, code, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteWithOpts runs code with optional network mode.
|
||||||
|
func (c *Client) ExecuteWithOpts(_ context.Context, language, code, networkMode string) (*ExecuteResult, error) {
|
||||||
|
// Use the low-level makeRequest to support network_mode
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"language": language,
|
||||||
|
"code": code,
|
||||||
|
}
|
||||||
|
if networkMode != "" {
|
||||||
|
data["network_mode"] = networkMode
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := makeRequest("POST", "/execute", c.Creds, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := toExecuteResult(resp)
|
||||||
|
|
||||||
|
// Poll if job is not terminal
|
||||||
|
if result.JobID != "" && result.Status != "completed" && result.Status != "failed" {
|
||||||
|
polled, err := WaitForJob(c.Creds, result.JobID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toExecuteResult(polled), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForJobResult polls a job until completion.
|
||||||
|
func (c *Client) WaitForJobResult(_ context.Context, jobID string) (*ExecuteResult, error) {
|
||||||
|
raw, err := WaitForJob(c.Creds, jobID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toExecuteResult(raw), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionResult holds typed session info.
|
||||||
|
type SessionResult struct {
|
||||||
|
ID string
|
||||||
|
Status string
|
||||||
|
Raw map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSession creates a new execution session.
|
||||||
|
func (c *Client) CreateSession(_ context.Context, language, networkMode string) (*SessionResult, error) {
|
||||||
|
opts := &SessionOptions{
|
||||||
|
Shell: language,
|
||||||
|
}
|
||||||
|
if networkMode != "" {
|
||||||
|
opts.NetworkMode = networkMode
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := CreateSession(c.Creds, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &SessionResult{Raw: raw}
|
||||||
|
if v, ok := raw["session_id"].(string); ok {
|
||||||
|
s.ID = v
|
||||||
|
}
|
||||||
|
if v, ok := raw["id"].(string); ok && s.ID == "" {
|
||||||
|
s.ID = v
|
||||||
|
}
|
||||||
|
if v, ok := raw["status"].(string); ok {
|
||||||
|
s.Status = v
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShellExec runs a command in an existing session.
|
||||||
|
func (c *Client) ShellExec(_ context.Context, sessionID, command string) (*ExecuteResult, error) {
|
||||||
|
raw, err := ShellSession(c.Creds, sessionID, command)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := toExecuteResult(raw)
|
||||||
|
|
||||||
|
// Poll if async
|
||||||
|
if result.JobID != "" && result.Status != "completed" && result.Status != "failed" {
|
||||||
|
polled, err := WaitForJob(c.Creds, result.JobID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toExecuteResult(polled), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroySession deletes a session.
|
||||||
|
func (c *Client) DestroySession(_ context.Context, sessionID string) error {
|
||||||
|
_, err := DeleteSession(c.Creds, sessionID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sessions returns all active sessions.
|
||||||
|
func (c *Client) Sessions(_ context.Context) ([]map[string]interface{}, error) {
|
||||||
|
return ListSessions(c.Creds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Services returns all services.
|
||||||
|
func (c *Client) Services(_ context.Context) ([]map[string]interface{}, error) {
|
||||||
|
return ListServices(c.Creds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceLogs retrieves logs for a service.
|
||||||
|
func (c *Client) ServiceLogs(_ context.Context, serviceID string) (string, error) {
|
||||||
|
raw, err := GetServiceLogs(c.Creds, serviceID, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if v, ok := raw["logs"].(string); ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckKeys verifies credentials are valid and returns key info.
|
||||||
|
func (c *Client) CheckKeys(_ context.Context) (map[string]interface{}, error) {
|
||||||
|
return ValidateKeys(c.Creds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InjectDirectory uploads a local directory to a session as a tarball.
|
||||||
|
// This is a higher-level operation that creates a tar.gz, base64-chunks it,
|
||||||
|
// and streams it into the session via shell commands.
|
||||||
|
func (c *Client) InjectDirectory(_ context.Context, sessionID, localDir, remoteDir string) error {
|
||||||
|
return fmt.Errorf("InjectDirectory not implemented in SDK client — use orchestra's upload package")
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
module unsandbox.com/un
|
module github.com/russellballestrini/un-inception/clients/go/sync/src
|
||||||
|
|
||||||
go 1.23.6
|
go 1.23.6
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue