Extism Go SDK

repository·main·Indexed 41 days ago

https://github.com/extism/go-sdk

A SDK for Go developers to integrate the Extism runtime into host applications, enabling the secure and efficient execution of WebAssembly (Wasm) plugins. It provides tools for initializing plugins via manifests, calling exported functions, implementing custom host functions, and managing plugin state and memory.

Tokens
6.1K
Snippets
24
Records
32
Agent score
50%

What's inside Extism Go SDK

  1. Manage Plug-in State

    main
    Plug-ins can be stateful or stateless. A stateful plug-in can maintain state between calls using internal variables. This state persists for the lifetime of the plug-in instance and is cleared when the plug-in is freed or a new one is initialized. Note that each individual plug-in instance maintains its own unique state.
  2. Implement Host Functions to extend Plug-in capabilities

    main

    Host functions allow you to grant new capabilities to plug-ins (like access to a database or a key-value store) by passing Go functions into the plug-in. Use extism.NewHostFunctionWithStack to define these functions. Inside the host function, you use the *extism.CurrentPlugin parameter (often named p) to read arguments from the stack or write results back to it using methods like ReadString, ReadBytes, and WriteBytes.

    // Example: A host function that reads a string from the stack
    kvRead := extism.NewHostFunctionWithStack(
        "kv_read",
        func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
            key, err := p.ReadString(stack[0])
            if err != nil {
                panic(err)
            }
    
            value, success := kvStore[key]
            if !success {
                value = []byte{0, 0, 0, 0}
            }
    
            stack[0], err = p.WriteBytes(value)
        },
        []ValueType{ValueTypePTR}, // Input types
        []ValueType{ValueTypePTR}, // Output types
    )
    
    // Pass the host function to NewPlugin
    plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{kvRead})
  3. Enable filesystem access for Wasm plugins

    main

    To allow a Wasm plugin to read or write files on the host system, you must:

    1. Set EnableWasi: true in the extism.PluginConfig.
    2. Map host directories to plugin-internal paths using the AllowedPaths field in the extism.Manifest. The map key is the 'plugin' path and the value is the 'host' path.
    manifest := extism.Manifest{
        AllowedPaths: map[string]string{
            // Maps the host directory "/home/user/data" to the plugin's "/mnt"
            "/mnt": "/home/user/data",
        },
        Wasm: []extism.Wasm{
            extism.WasmFile{Path: "fs_plugin.wasm"},
        },
    }
    
    config := extism.PluginConfig{
        EnableWasi: true,
    }
    
    plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{})
  4. Pre-compile plugins for concurrent usage

    main

    If you need to create many instances of the same plug-in (e.g., in a high-concurrency environment), use extism.NewCompiledPlugin to compile the module once. You can then efficiently create multiple independent instances from that single compiled object using compiledPlugin.Instance(ctx, extism.PluginInstanceConfig{}). Each instance will maintain its own separate state.

    // 1. Compile once
    compiledPlugin, err := extism.NewCompiledPlugin(ctx, manifest, config, []extism.HostFunction{})
    
    // 2. Create instances in goroutines
    for i := 0; i < 3; i++ {
        go func() {
            plugin, err := compiledPlugin.Instance(ctx, extism.PluginInstanceConfig{})
            if err != nil {
                return
            }
            defer plugin.Close(ctx)
            // ... call plugin
        }()
    }
  5. Build example plugins using TinyGo

    main

    The example plugins in this repository are written in Go and require TinyGo to compile them into WebAssembly (WASM) targets. To build a specific plugin (e.g., the config plugin), navigate to its directory and use the tinygo build command with the -target wasi flag.

    cd plugins/config
    tinygo build -target wasi -o ../wasm/config.wasm main.go
  6. Optimize performance with Compilation Cache

    main

    To speed up subsequent initializations of Wasm modules, you can enable a compilation cache using the underlying Wazero runtime. This involves creating a wazero.NewCompilationCache() and passing it into the RuntimeConfig within your extism.PluginConfig.

    ctx := context.Background()
    cache := wazero.NewCompilationCache()
    // Ensure the cache is not closed until all dependent plugins are finished
    defer cache.Close(ctx)
    
    config := extism.PluginConfig{
        EnableWasi:    true,
        ModuleConfig:  wazero.NewModuleConfig(),
        RuntimeConfig: wazero.NewRuntimeConfig().WithCompilationCache(cache),
    }
    
    _, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{})
  7. Initialize an Extism Plug-in

    main

    A plug-in is a code module stored in a .wasm file. You can load plug-ins from a local file on disk or from a URL. Use extism.NewPlugin to initialize a plug-in by providing a context.Context, an extism.Manifest (which defines the Wasm source), an extism.PluginConfig, and a slice of extism.HostFunctions.

    package main
    
    import (
    	"context"
    	"fmt"
    	"github.com/extism/go-sdk"
    	"os"
    )
    
    func main() {
    	manifest := extism.Manifest{
    		Wasm: []extism.Wasm{
    			extism.WasmUrl{
    				Url: "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm",
    			},
    		},
    	}
    
    	ctx := context.Background()
    	config := extism.PluginConfig{}
    	plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{})
    
    	if err != nil {
    		fmt.Printf("Failed to initialize plugin: %v\n", err)
    		os.Exit(1)
    	}
    }
  8. Define Wasm sources in a Manifest

    main

    The Manifest supports three ways to provide WebAssembly data via the Wasm interface:

    1. WasmData: In-memory byte arrays.
    2. WasmFile: Loading from a local file path.
    3. WasmUrl: Fetching from a remote URL with optional HTTP methods and headers.

    Each can optionally include a Hash for verification and a Name for identification.

    // Example of different Wasm sources
    wasmSources := []extism.Wasm{
        extism.WasmData{Data: []byte{0x00, 0x01}, Name: "in-memory"},
        extism.WasmFile{Path: "./plugin.wasm", Name: "local-file"},
        extism.WasmUrl{Url: "https://example.com/plugin.wasm", Method: "GET", Name: "remote"},
    }
  9. How HostFunctionStackCallback works

    main

    A HostFunctionStackCallback is a Go function that acts as a host-side implementation of a function called by a WebAssembly plugin.

    Signature: func(ctx context.Context, p *CurrentPlugin, stack []uint64)

    Behavior:

    1. Parameters: Read input parameters from the stack slice using DecodeXXX functions. The index in the stack corresponds to the parameter order.
    2. Plugin Access: Use the p *CurrentPlugin argument to access the plugin's memory (via p.Memory()), log messages (p.Log()), or manage memory (p.Alloc(), p.Free()).
    3. Results: Write return values back into the stack slice starting at index zero.
    4. Lifecycle: The stack must not be used after the function returns.

    Example Pattern:

    // Read two I32 parameters and return one I32 result
    func(ctx context.Context, p *CurrentPlugin, stack []uint64) {
        val1 := DecodeI32(stack[0])
        val2 := DecodeI32(stack[1])
        
        stack[0] = EncodeI32(val1 + val2)
    }
  10. Configure a plugin using a Manifest

    main

    A Manifest defines the configuration for an Extism plugin, including which WebAssembly modules to load, memory limits, environment variables, and security constraints like allowed hosts and file paths. You can define Wasm modules using in-memory data, local files, or remote URLs.

    manifest := extism.Manifest{
    	Wasm: []extism.Wasm{
    		extism.WasmFile{Path: "plugin.wasm"},
    	},
    	Config: map[string]string{
    		"key": "value",
    	},
    	AllowedHosts: []string{"example.com"},
    	AllowedPaths: map[string]string{"/tmp": "rw"},
    }
  11. Configure filesystem access via AllowedPaths

    main

    You can restrict or provide filesystem access to a plugin using the AllowedPaths field in the Manifest. Paths prefixed with ro: are mounted as read-only directories.

    When instantiating a plugin, these paths are applied to the module's filesystem configuration. Note that if AllowedPaths is used, the filesystem configuration provided in PluginConfig.ModuleConfig is ignored.