Embedding Shai with Golang

Embedding Shai with Golang

Embed Shai inside Go applications for programmatic sandbox control.

Installation

1go get github.com/colony-2/shai/pkg/shai

Quick Start

 1package main
 2
 3import (
 4    "context"
 5    "log"
 6
 7    shai "github.com/colony-2/shai/pkg/shai"
 8)
 9
10func main() {
11    ctx := context.Background()
12
13    // Load config from workspace
14    cfg, err := shai.LoadSandboxConfig("/path/to/workspace",
15        shai.WithReadWritePaths([]string{"src/components"}),
16        shai.WithResourceSets([]string{"frontend-dev"}),
17        shai.WithPlatformOverride("linux/amd64"),
18    )
19    if err != nil {
20        log.Fatal(err)
21    }
22
23    // Set command to run
24    cfg.PostSetupExec = &shai.SandboxExec{
25        Command: []string{"npm", "test"},
26        Workdir: "/src",
27        UseTTY:  false,
28    }
29
30    // Create and run sandbox
31    sandbox, err := shai.NewSandbox(cfg)
32    if err != nil {
33        log.Fatal(err)
34    }
35    defer sandbox.Close()
36
37    if err := sandbox.Run(ctx); err != nil {
38        log.Fatal(err)
39    }
40}

Core Types

SandboxConfig

Configuration for the sandbox:

 1type SandboxConfig struct {
 2    WorkingDir         string
 3    ConfigFile         string
 4    ReadWritePaths     []string
 5    ResourceSets       []string
 6    PrependResourceSet *ResourceSet
 7    AppendResourceSet  *ResourceSet
 8    TemplateVars       map[string]string
 9    PostSetupExec      *SandboxExec
10    ImageOverride      string
11    PlatformOverride   string
12    UserOverride       string
13    Verbose            bool
14    ShowScriptOutput   bool
15}

SandboxExec

Command execution specification:

1type SandboxExec struct {
2    Command []string          // Command and args
3    Env     map[string]string // Additional env vars
4    Workdir string            // Working directory
5    UseTTY  bool              // Allocate TTY
6}

ResourceSet

Inline resources you can prepend/append from the Go API:

1type ResourceSet struct {
2    Vars         []VarMapping
3    Mounts       []Mount
4    Calls        []Call
5    HTTP         []string
6    Ports        []Port
7    RootCommands []string
8    Options      ResourceOptions
9}

Sandbox Interface

1type Sandbox interface {
2    Run(ctx context.Context) error        // Run and wait
3    Start(ctx context.Context) (SandboxSession, error)  // Start without waiting
4    Close() error                          // Cleanup
5}

SandboxSession

Long-running sandbox session:

1type SandboxSession interface {
2    ContainerID() string               // Get container ID
3    Wait() error                       // Wait for completion
4    Stop(timeout time.Duration) error  // Stop gracefully
5    Close() error                      // Cleanup
6}

Common Patterns

Running Tests

 1cfg, _ := shai.LoadSandboxConfig(workspacePath,
 2    shai.WithReadWritePaths([]string{"coverage"}),
 3)
 4
 5cfg.PrependResourceSet = &shai.ResourceSet{
 6    HTTP: []string{"registry.npmjs.org"},
 7}
 8
 9cfg.PostSetupExec = &shai.SandboxExec{
10    Command: []string{"go", "test", "./..."},
11    UseTTY:  false,
12}
13
14sandbox, _ := shai.NewSandbox(cfg)
15defer sandbox.Close()
16
17if err := sandbox.Run(ctx); err != nil {
18    // Tests failed
19}

Long-Running Process

 1sandbox, _ := shai.NewSandbox(cfg)
 2defer sandbox.Close()
 3
 4// Start without waiting
 5session, err := sandbox.Start(ctx)
 6if err != nil {
 7    log.Fatal(err)
 8}
 9defer session.Close()
10
11log.Printf("Container: %s", session.ContainerID())
12
13// Do other work...
14
15// Wait for completion
16if err := session.Wait(); err != nil {
17    log.Fatal(err)
18}

Multiple Sandboxes

 1// Run multiple sandboxes concurrently
 2var wg sync.WaitGroup
 3
 4for _, component := range components {
 5    wg.Add(1)
 6    go func(comp string) {
 7        defer wg.Done()
 8
 9        cfg, _ := shai.LoadSandboxConfig(workspacePath,
10            shai.WithReadWritePaths([]string{comp}),
11        )
12
13        sandbox, _ := shai.NewSandbox(cfg)
14        defer sandbox.Close()
15
16        sandbox.Run(ctx)
17    }(component)
18}
19
20wg.Wait()

With Template Variables

1cfg, _ := shai.LoadSandboxConfig(workspacePath,
2    shai.WithTemplateVars(map[string]string{
3        "ENV":    "staging",
4        "REGION": "us-east-1",
5    }),
6)

With Platform Override

1cfg, _ := shai.LoadSandboxConfig(workspacePath,
2    shai.WithPlatformOverride("linux/amd64"),
3)

Error Handling

 1cfg, err := shai.LoadSandboxConfig(workspacePath)
 2if err != nil {
 3    // Config loading failed
 4    // - Config file invalid
 5    // - Template vars missing
 6    // - Workspace doesn't exist
 7    log.Fatal(err)
 8}
 9
10sandbox, err := shai.NewSandbox(cfg)
11if err != nil {
12    // Sandbox creation failed
13    // - Docker not available
14    // - Image pull failed
15    // - Invalid configuration
16    log.Fatal(err)
17}
18
19if err := sandbox.Run(ctx); err != nil {
20    // Execution failed
21    // - Command failed
22    // - Container crashed
23    // - Network error
24    log.Fatal(err)
25}

Testing with Shai

Use Shai in integration tests:

 1func TestMyApp(t *testing.T) {
 2    ctx := context.Background()
 3
 4    cfg, err := shai.LoadSandboxConfig(".",
 5        shai.WithReadWritePaths([]string{"tmp"}),
 6    )
 7    require.NoError(t, err)
 8
 9    cfg.PostSetupExec = &shai.SandboxExec{
10        Command: []string{"./test-script.sh"},
11    }
12
13    sandbox, err := shai.NewSandbox(cfg)
14    require.NoError(t, err)
15    defer sandbox.Close()
16
17    err = sandbox.Run(ctx)
18    assert.NoError(t, err)
19}

Advanced: Custom Logging

1import "os"
2
3cfg.StdoutWriter = os.Stdout
4cfg.StderrWriter = os.Stderr
5cfg.Verbose = true
6
7sandbox, _ := shai.NewSandbox(cfg)
8// All output goes to configured writers

See Also