sdk

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 30, 2026 License: EUPL-1.2 Imports: 9 Imported by: 0

Documentation

Overview

Package sdk provides the public API for building AWF plugins.

This package contains interfaces, types, and utilities for developing plugins that extend AWF workflow capabilities. Plugin developers import this package to create custom operations that integrate with AWF workflows.

Key features:

  • Plugin interface with lifecycle methods (Init, Shutdown)
  • OperationHandler and OperationProvider interfaces for custom operations
  • BasePlugin for embedding (provides default implementations)
  • OperationResult for structured operation responses
  • Input helpers for extracting typed values from operation inputs
  • Schema types for declaring operation signatures

Core Interfaces

## Plugin (sdk.go)

Plugin interface defines the plugin lifecycle:

  • Name: Unique plugin identifier
  • Version: Semantic version string
  • Init: Initialize with configuration
  • Shutdown: Graceful cleanup on exit

## OperationHandler (sdk.go)

OperationHandler processes single operation requests:

  • Handle: Execute operation with inputs, return outputs

## OperationProvider (sdk.go)

OperationProvider for plugins offering multiple operations:

  • Operations: List of operation names
  • HandleOperation: Execute named operation with inputs

Base Types

## BasePlugin (sdk.go)

BasePlugin provides minimal implementation for embedding:

  • Implements Plugin interface with no-op Init/Shutdown
  • Stores PluginName and PluginVersion
  • Embed in your plugin struct to inherit base behavior

## OperationResult (sdk.go)

OperationResult holds execution results:

  • Success: Boolean success flag
  • Output: Human-readable output text
  • Data: Structured output data (map[string]any)
  • Error: Error message if failed
  • ToMap: Convert to map for handler return values

Helper Functions

## Result Constructors (sdk.go)

NewSuccessResult(output string, data map[string]any) *OperationResult
NewErrorResult(errMsg string) *OperationResult
NewErrorResultf(format string, args ...any) *OperationResult

## Input Extractors (sdk.go)

Type-safe input extraction with optional defaults:

GetString(inputs map[string]any, key string) (string, bool)
GetStringDefault(inputs map[string]any, key, defaultValue string) string
GetInt(inputs map[string]any, key string) (int, bool)
GetIntDefault(inputs map[string]any, key string, defaultValue int) int
GetBool(inputs map[string]any, key string) (value, ok bool)
GetBoolDefault(inputs map[string]any, key string, defaultValue bool) bool

Schema Types

## InputSchema (sdk.go)

InputSchema defines an operation input parameter:

  • Type: string, integer, boolean, array, object
  • Required: Whether parameter is mandatory
  • Default: Default value if not provided
  • Description: Human-readable description
  • Validation: Optional validation rule (url, email, etc.)

## OperationSchema (sdk.go)

OperationSchema defines a plugin-provided operation:

  • Name: Operation name (e.g., "slack.send")
  • Description: Human-readable description
  • Inputs: Map of input parameter schemas
  • Outputs: List of output field names

## Schema Helpers (sdk.go)

RequiredInput(inputType, description string) InputSchema
OptionalInput(inputType, description string, defaultValue any) InputSchema
IsValidInputType(t string) bool

Constants

## Input Types (sdk.go)

InputTypeString  = "string"
InputTypeInteger = "integer"
InputTypeBoolean = "boolean"
InputTypeArray   = "array"
InputTypeObject  = "object"

## Errors (sdk.go)

ErrNotImplemented   # Stub method needs implementation
ErrInvalidInput     # Invalid operation input
ErrOperationFailed  # Operation execution failed

Usage Examples

## Simple Plugin with Single Operation

package main

import (
    "context"
    "github.com/awf-project/cli/pkg/plugin/sdk"
)

type SlackPlugin struct {
    sdk.BasePlugin
    webhookURL string
}

func NewSlackPlugin() *SlackPlugin {
    return &SlackPlugin{
        BasePlugin: sdk.BasePlugin{
            PluginName:    "slack",
            PluginVersion: "1.0.0",
        },
    }
}

func (p *SlackPlugin) Init(ctx context.Context, config map[string]any) error {
    url, ok := sdk.GetString(config, "webhook_url")
    if !ok {
        return sdk.ErrInvalidInput
    }
    p.webhookURL = url
    return nil
}

func (p *SlackPlugin) Handle(ctx context.Context, inputs map[string]any) (map[string]any, error) {
    message := sdk.GetStringDefault(inputs, "message", "")
    if message == "" {
        return sdk.NewErrorResult("message is required").ToMap(), nil
    }

    // Send message to Slack (implementation omitted)
    err := p.sendMessage(message)
    if err != nil {
        return sdk.NewErrorResult(err.Error()).ToMap(), nil
    }

    result := sdk.NewSuccessResult("Message sent", map[string]any{
        "timestamp": time.Now().Unix(),
    })
    return result.ToMap(), nil
}

## Plugin with Multiple Operations

type GithubPlugin struct {
    sdk.BasePlugin
    token string
}

func (p *GithubPlugin) Operations() []string {
    return []string{"github.create_issue", "github.list_repos"}
}

func (p *GithubPlugin) HandleOperation(ctx context.Context, name string, inputs map[string]any) (*sdk.OperationResult, error) {
    switch name {
    case "github.create_issue":
        return p.createIssue(ctx, inputs)
    case "github.list_repos":
        return p.listRepos(ctx, inputs)
    default:
        return sdk.NewErrorResult("unknown operation"), nil
    }
}

func (p *GithubPlugin) createIssue(ctx context.Context, inputs map[string]any) (*sdk.OperationResult, error) {
    title := sdk.GetStringDefault(inputs, "title", "")
    body := sdk.GetStringDefault(inputs, "body", "")

    // Create issue via GitHub API (implementation omitted)
    issueNumber, err := p.api.CreateIssue(title, body)
    if err != nil {
        return sdk.NewErrorResult(err.Error()), nil
    }

    return sdk.NewSuccessResult("Issue created", map[string]any{
        "issue_number": issueNumber,
    }), nil
}

## Operation Schema Declaration

func (p *SlackPlugin) Schema() sdk.OperationSchema {
    return sdk.OperationSchema{
        Name:        "slack.send",
        Description: "Send message to Slack channel",
        Inputs: map[string]sdk.InputSchema{
            "message": sdk.RequiredInput(sdk.InputTypeString, "Message text"),
            "channel": sdk.OptionalInput(sdk.InputTypeString, "Target channel", "#general"),
            "urgent":  sdk.OptionalInput(sdk.InputTypeBoolean, "Urgent notification", false),
        },
        Outputs: []string{"timestamp", "channel_id"},
    }
}

## Input Validation and Extraction

func (p *Plugin) Handle(ctx context.Context, inputs map[string]any) (map[string]any, error) {
    // Required string input
    name, ok := sdk.GetString(inputs, "name")
    if !ok {
        return sdk.NewErrorResult("name is required").ToMap(), nil
    }

    // Optional integer input with default
    count := sdk.GetIntDefault(inputs, "count", 10)

    // Optional boolean input
    verbose, _ := sdk.GetBool(inputs, "verbose")

    // Execute operation
    result := performOperation(name, count, verbose)

    return sdk.NewSuccessResult("Done", map[string]any{
        "processed": result,
    }).ToMap(), nil
}

## Error Handling

func (p *Plugin) Handle(ctx context.Context, inputs map[string]any) (map[string]any, error) {
    url, ok := sdk.GetString(inputs, "url")
    if !ok {
        // Invalid input - return error result
        return sdk.NewErrorResult("url is required").ToMap(), nil
    }

    data, err := fetchURL(url)
    if err != nil {
        // Operation failed - return error result with formatted message
        return sdk.NewErrorResultf("fetch failed: %v", err).ToMap(), nil
    }

    return sdk.NewSuccessResult("Fetched", map[string]any{
        "size": len(data),
    }).ToMap(), nil
}

## Graceful Shutdown

func (p *DatabasePlugin) Shutdown(ctx context.Context) error {
    // Close database connections
    if p.db != nil {
        return p.db.Close()
    }
    return nil
}

Workflow Integration

Plugins are used in workflows via the operation step type:

steps:
  send_notification:
    type: operation
    operation: slack.send
    inputs:
      message: "Deployment completed"
      channel: "#alerts"
      urgent: true
    on_success: end

Plugin output is available in subsequent steps:

{{states.send_notification.Data.timestamp}}
{{states.send_notification.Output}}

Testing Support

## MockPlugin (testing.go)

MockPlugin provides a test double for plugin testing:

  • Implements Plugin and OperationHandler interfaces
  • Configurable responses for testing workflows
  • Call tracking for verification

Example usage:

mock := sdk.NewMockPlugin("test-plugin", "1.0.0")
mock.SetResponse("success output", map[string]any{"key": "value"})

result, err := mock.Handle(ctx, inputs)
// result contains configured response

Design Principles

## Public API Stability

This is the stable plugin SDK for external developers:

  • Semantic versioning for breaking changes
  • Backward compatibility for minor/patch releases
  • Clear deprecation warnings before removal

## Minimal Dependencies

SDK has minimal external dependencies:

  • Standard library only (context, errors)
  • No AWF internal packages imported
  • Plugin authors control their own dependencies

## Simple Interface

Easy to implement, hard to misuse:

  • Single Handle method for simple plugins
  • Optional OperationProvider for multi-operation plugins
  • BasePlugin provides sensible defaults
  • Helper functions reduce boilerplate

## Type Safety

Structured types for clarity:

  • OperationResult enforces success/error pattern
  • InputSchema declares expected types
  • Helper functions handle type conversions

See also:

  • internal/domain/operation: Domain operation interface and result types
  • internal/infrastructure/pluginmgr: Plugin loader and registry implementation
  • docs/plugin-development.md: Complete plugin development guide
  • examples/plugins/: Example plugin implementations

Package sdk provides the public API for AWF plugin authors.

This package contains interfaces and utilities that plugin developers use to create AWF-compatible plugins. Import this package to build plugins that integrate with AWF workflows.

Example usage:

type MyPlugin struct {
    sdk.BasePlugin
}

func (p *MyPlugin) Init(ctx context.Context, config map[string]any) error {
    // Initialize your plugin
    return nil
}

// Implement OperationHandler for custom operations
func (p *MyPlugin) Handle(ctx context.Context, inputs map[string]any) (map[string]any, error) {
    result := sdk.NewSuccessResult("done", nil)
    return result.ToMap(), nil
}

Package sdk provides testing utilities for AWF plugin development.

Index

Constants

View Source
const (
	InputTypeString  = "string"
	InputTypeInteger = "integer"
	InputTypeBoolean = "boolean"
	InputTypeArray   = "array"
	InputTypeObject  = "object"
)

Input types for operation parameters.

Variables

View Source
var (
	// ErrNotImplemented indicates a stub method that needs implementation.
	ErrNotImplemented = errors.New("not implemented")
	// ErrInvalidInput indicates invalid operation input.
	ErrInvalidInput = errors.New("invalid input")
	// ErrOperationFailed indicates the operation failed to execute.
	ErrOperationFailed = errors.New("operation failed")
)

Common errors for plugin implementations.

View Source
var Handshake = goplugin.HandshakeConfig{

	MagicCookieKey:   "AWF_PLUGIN",
	MagicCookieValue: "awf-plugin-v1",
	ProtocolVersion:  1,
}

Handshake is the shared handshake config between AWF host and plugins. Plugin binaries must use this config to be recognized by AWF.

ValidInputTypes lists all recognized input types.

Functions

func GetBool

func GetBool(inputs map[string]any, key string) (value, ok bool)

func GetBoolDefault

func GetBoolDefault(inputs map[string]any, key string, defaultValue bool) bool

func GetInt

func GetInt(inputs map[string]any, key string) (int, bool)

func GetIntDefault

func GetIntDefault(inputs map[string]any, key string, defaultValue int) int

func GetString

func GetString(inputs map[string]any, key string) (string, bool)

func GetStringDefault

func GetStringDefault(inputs map[string]any, key, defaultValue string) string

func IsValidInputType

func IsValidInputType(t string) bool

func Serve

func Serve(p Plugin)

Serve starts the plugin process, blocking until the host disconnects. Plugin binaries call this from their main() after constructing their Plugin.

func TestConfig

func TestConfig(keyValues ...any) map[string]any

func TestInputs

func TestInputs(keyValues ...any) map[string]any

Types

type BasePlugin

type BasePlugin struct {
	PluginName    string
	PluginVersion string
}

BasePlugin provides a minimal implementation for embedding in plugins.

func (*BasePlugin) Init

func (p *BasePlugin) Init(_ context.Context, _ map[string]any) error

func (*BasePlugin) Name

func (p *BasePlugin) Name() string

func (*BasePlugin) Shutdown

func (p *BasePlugin) Shutdown(_ context.Context) error

func (*BasePlugin) Version

func (p *BasePlugin) Version() string

type GRPCPluginBridge

type GRPCPluginBridge struct {
	goplugin.NetRPCUnsupportedPlugin
	// contains filtered or unexported fields
}

GRPCPluginBridge adapts sdk.Plugin to the go-plugin GRPCPlugin interface. Implementation in grpc_plugin.go bridges go-plugin interface to gRPC servers/clients.

func (*GRPCPluginBridge) GRPCClient

func (b *GRPCPluginBridge) GRPCClient(_ context.Context, _ *goplugin.GRPCBroker, _ *grpc.ClientConn) (interface{}, error)

GRPCClient is required by the go-plugin GRPCPlugin interface but is never called on the plugin side. The host uses its own client implementation.

func (*GRPCPluginBridge) GRPCServer

func (b *GRPCPluginBridge) GRPCServer(_ *goplugin.GRPCBroker, s *grpc.Server) (err error)

GRPCServer implements the go-plugin GRPCPlugin interface by registering the PluginService and OperationService gRPC servers.

type InputSchema

type InputSchema struct {
	Type        string
	Required    bool
	Default     any
	Description string
	Validation  string
}

func OptionalInput

func OptionalInput(inputType, description string, defaultValue any) InputSchema

func RequiredInput

func RequiredInput(inputType, description string) InputSchema

type MockOperationHandler

type MockOperationHandler struct {
	Outputs    map[string]any
	Error      error
	LastInputs map[string]any
	CallCount  int
	HandleFunc func(ctx context.Context, inputs map[string]any) (map[string]any, error)
	// contains filtered or unexported fields
}

MockOperationHandler is a test implementation of OperationHandler.

func NewMockOperationHandler

func NewMockOperationHandler() *MockOperationHandler

func (*MockOperationHandler) GetCallCount

func (m *MockOperationHandler) GetCallCount() int

func (*MockOperationHandler) Handle

func (m *MockOperationHandler) Handle(ctx context.Context, inputs map[string]any) (map[string]any, error)

Handle executes the operation with recording.

func (*MockOperationHandler) Reset

func (m *MockOperationHandler) Reset()

type MockOperationProvider

type MockOperationProvider struct {
	OperationNames []string
	Results        map[string]*OperationResult
	Errors         map[string]error
	LastOperation  string
	LastInputs     map[string]any
	HandleFunc     func(ctx context.Context, name string, inputs map[string]any) (*OperationResult, error)
	// contains filtered or unexported fields
}

MockOperationProvider is a test implementation of OperationProvider.

func NewMockOperationProvider

func NewMockOperationProvider(operations ...string) *MockOperationProvider

func (*MockOperationProvider) HandleOperation

func (m *MockOperationProvider) HandleOperation(ctx context.Context, name string, inputs map[string]any) (*OperationResult, error)

func (*MockOperationProvider) Operations

func (m *MockOperationProvider) Operations() []string

Operations returns the list of operation names.

func (*MockOperationProvider) SetCommandResult

func (m *MockOperationProvider) SetCommandResult(operation string, result *OperationResult)

func (*MockOperationProvider) SetError

func (m *MockOperationProvider) SetError(operation string, err error)

func (*MockOperationProvider) SetResult

func (m *MockOperationProvider) SetResult(operation string, result *OperationResult)

type MockPlugin

type MockPlugin struct {
	PluginName    string
	PluginVersion string

	InitCalled     bool
	ShutdownCalled bool
	LastConfig     map[string]any
	InitError      error
	ShutdownError  error
	// contains filtered or unexported fields
}

MockPlugin is a test implementation of the Plugin interface.

func NewMockPlugin

func NewMockPlugin(name, version string) *MockPlugin

func (*MockPlugin) Init

func (m *MockPlugin) Init(_ context.Context, config map[string]any) error

Init records the call and returns the configured error.

func (*MockPlugin) Name

func (m *MockPlugin) Name() string

func (*MockPlugin) Reset

func (m *MockPlugin) Reset()

func (*MockPlugin) Shutdown

func (m *MockPlugin) Shutdown(_ context.Context) error

Shutdown records the call and returns the configured error.

func (*MockPlugin) Version

func (m *MockPlugin) Version() string

func (*MockPlugin) WasInitCalled

func (m *MockPlugin) WasInitCalled() bool

WasInitCalled returns whether Init was called (thread-safe).

func (*MockPlugin) WasShutdownCalled

func (m *MockPlugin) WasShutdownCalled() bool

type OperationHandler

type OperationHandler interface {
	Handle(ctx context.Context, inputs map[string]any) (map[string]any, error)
}

type OperationProvider

type OperationProvider interface {
	Operations() []string
	HandleOperation(ctx context.Context, name string, inputs map[string]any) (*OperationResult, error)
}

type OperationResult

type OperationResult struct {
	Success bool
	Output  string
	Data    map[string]any
	Error   string
}

func NewErrorResult

func NewErrorResult(errMsg string) *OperationResult

func NewErrorResultf

func NewErrorResultf(format string, args ...any) *OperationResult

func NewSuccessResult

func NewSuccessResult(output string, data map[string]any) *OperationResult

func (*OperationResult) ToMap

func (r *OperationResult) ToMap() map[string]any

type OperationSchema

type OperationSchema struct {
	Name        string
	Description string
	Inputs      map[string]InputSchema
	Outputs     []string
}

type Plugin

type Plugin interface {
	Name() string
	Version() string
	Init(ctx context.Context, config map[string]any) error
	Shutdown(ctx context.Context) error
}

type Severity added in v0.5.0

type Severity int

Severity of a validation issue. SeverityError (zero value) matches proto SEVERITY_UNSPECIFIED being treated as ERROR by the host.

const (
	SeverityError   Severity = 0
	SeverityWarning Severity = 1
	SeverityInfo    Severity = 2
)

type StepDefinition added in v0.5.0

type StepDefinition struct {
	Type        string         `json:"type"`
	Description string         `json:"description,omitempty"`
	Command     string         `json:"command,omitempty"`
	Operation   string         `json:"operation,omitempty"`
	Timeout     int            `json:"timeout,omitempty"`
	OnSuccess   string         `json:"on_success,omitempty"`
	OnFailure   string         `json:"on_failure,omitempty"`
	Config      map[string]any `json:"config,omitempty"`
}

StepDefinition is the SDK representation of a workflow step for validator plugins.

type StepExecuteRequest added in v0.5.0

type StepExecuteRequest struct {
	StepName string
	StepType string
	Config   map[string]any
	Inputs   map[string]any
}

StepExecuteRequest carries the execution context for a custom step type.

type StepExecuteResult added in v0.5.0

type StepExecuteResult struct {
	Output   string
	Data     map[string]any
	ExitCode int32
}

StepExecuteResult holds the result of a custom step type execution.

type StepTypeHandler added in v0.5.0

type StepTypeHandler interface {
	StepTypes() []StepTypeInfo
	ExecuteStep(ctx context.Context, req StepExecuteRequest) (StepExecuteResult, error)
}

StepTypeHandler is the plugin-author interface for custom step types. Implement this interface to add new step types available in workflow YAML via `type:`.

StepTypes is called once after Init() to register available types. ExecuteStep is called each time a workflow step with a matching type runs.

type StepTypeInfo added in v0.5.0

type StepTypeInfo struct {
	Name        string
	Description string
}

StepTypeInfo describes a custom step type registered by a plugin.

type ValidationIssue added in v0.5.0

type ValidationIssue struct {
	Severity Severity
	Message  string
	Step     string // step name, empty for workflow-level issues
	Field    string // field name within step, empty if not applicable
}

ValidationIssue describes a single validation problem found by a validator plugin.

type Validator added in v0.5.0

type Validator interface {
	ValidateWorkflow(ctx context.Context, workflow WorkflowDefinition) ([]ValidationIssue, error)
	ValidateStep(ctx context.Context, workflow WorkflowDefinition, stepName string) ([]ValidationIssue, error)
}

Validator is the plugin-author interface for custom workflow validation. Implement this interface to add validation rules that run during `awf validate`.

The host calls ValidateWorkflow once per validation run, then ValidateStep for each step in the workflow. Return nil issues for no problems.

type WorkflowDefinition added in v0.5.0

type WorkflowDefinition struct {
	Name        string                    `json:"name"`
	Description string                    `json:"description,omitempty"`
	Version     string                    `json:"version,omitempty"`
	Author      string                    `json:"author,omitempty"`
	Tags        []string                  `json:"tags,omitempty"`
	Initial     string                    `json:"initial"`
	Steps       map[string]StepDefinition `json:"steps"`
}

WorkflowDefinition is the SDK representation of a workflow for validator plugins. It is deserialized from the JSON payload sent by the host during validation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL