Skip to content

System Ports Reference

Complete reference for system ports in TinySystems components.

Overview

System ports are special ports that provide infrastructure functionality beyond regular message handling.

PortConstantPurpose
_settingsv1alpha1.SettingsPortComponent configuration
_controlv1alpha1.ControlPortUI interaction (buttons)
_reconcilev1alpha1.ReconcilePortKubernetes reconciliation
_clientv1alpha1.ClientPortResource manager access

_settings Port

Receives component configuration when a TinyNode is created or updated.

Constant

go
v1alpha1.SettingsPort = "_settings"

Purpose

  • Initial component configuration
  • Runtime configuration updates
  • User-provided settings

Port Definition

go
{
    Name:          v1alpha1.SettingsPort,
    Label:         "Settings",
    Position:      module.PositionTop,
    Source:        true,
    Configuration: Settings{},
}

Message Type

Your custom Settings struct:

go
type Settings struct {
    Timeout    string `json:"timeout" default:"30s"`
    MaxRetries int    `json:"maxRetries" default:"3"`
    Debug      bool   `json:"debug" default:"false"`
}

Handler Pattern

go
func (c *Component) Handle(ctx context.Context, output Handler, port string, msg any) error {
    switch port {
    case v1alpha1.SettingsPort:
        c.settings = msg.(Settings)
        // Apply configuration
        c.timeout, _ = time.ParseDuration(c.settings.Timeout)
        return nil
    // ... other ports
    }
}

When Triggered

  • TinyNode created
  • TinyNode spec.edges configuration changes
  • Settings edge data changes

_control Port

Handles UI interactions like button clicks.

Constant

go
v1alpha1.ControlPort = "_control"

Purpose

  • Start/Stop actions
  • Manual triggers
  • State display
  • Button interactions

Port Definition

go
{
    Name:          v1alpha1.ControlPort,
    Label:         "Control",
    Position:      module.PositionTop,
    Source:        true,
    Configuration: ControlState{
        Status: "stopped",
        Start:  false,
        Stop:   false,
    },
}

Control State Pattern

go
type ControlState struct {
    // Display fields (shown in UI)
    Status    string `json:"status" readonly:"true" title:"Status"`
    LastRun   string `json:"lastRun" readonly:"true" title:"Last Run"`

    // Button fields
    Start bool `json:"start" format:"button" title:"Start" colSpan:"col-span-6"`
    Stop  bool `json:"stop" format:"button" title:"Stop" colSpan:"col-span-6"`
}

Handler Interface

Implement ControlHandler:

go
type ControlHandler interface {
    HandleControl(ctx context.Context, state any, port string, msg any) (any, error)
}

Handler Pattern

go
func (c *Component) HandleControl(
    ctx context.Context,
    state any,
    port string,
    msg any,
) (any, error) {
    controlState := state.(*ControlState)

    switch port {
    case "start":
        if !utils.IsLeader(ctx) {
            return controlState, nil
        }
        c.start()
        return &ControlState{
            Status:  "running",
            LastRun: time.Now().Format(time.RFC3339),
        }, nil

    case "stop":
        if !utils.IsLeader(ctx) {
            return controlState, nil
        }
        c.stop()
        return &ControlState{
            Status: "stopped",
        }, nil
    }

    return controlState, nil
}

When Triggered

  • User clicks button in UI
  • Button field set to true in edge configuration

_reconcile Port

Receives Kubernetes reconciliation events.

Constant

go
v1alpha1.ReconcilePort = "_reconcile"

Purpose

  • React to TinyNode changes
  • Access TinyNode metadata
  • Update TinyNode status
  • State propagation across replicas

Port Definition

go
{
    Name:          v1alpha1.ReconcilePort,
    Label:         "Reconcile",
    Position:      module.PositionTop,
    Source:        true,
    Configuration: v1alpha1.TinyNode{},
}

Message Type

*v1alpha1.TinyNode - the current TinyNode resource

Handler Pattern

go
func (c *Component) Handle(ctx context.Context, output Handler, port string, msg any) error {
    switch port {
    case v1alpha1.ReconcilePort:
        node := msg.(*v1alpha1.TinyNode)

        // Read metadata from other replicas
        if node.Status.Metadata != nil {
            if port, ok := node.Status.Metadata["server-port"]; ok {
                c.serverPort = port
            }
        }

        // Leader updates metadata
        if utils.IsLeader(ctx) {
            return output(ctx, v1alpha1.ReconcilePort, func(n *v1alpha1.TinyNode) {
                if n.Status.Metadata == nil {
                    n.Status.Metadata = make(map[string]string)
                }
                n.Status.Metadata["server-port"] = c.serverPort
            })
        }
        return nil
    }
}

Output Pattern

Output a mutation function to update TinyNode:

go
output(ctx, v1alpha1.ReconcilePort, func(node *v1alpha1.TinyNode) {
    // Modify node.Status
    node.Status.Metadata["key"] = "value"
})

When Triggered

  • TinyNode created
  • TinyNode updated
  • Periodic reconciliation (every 5 minutes by default)
  • Manual refresh triggered

_client Port

Provides access to the resource manager for Kubernetes operations.

Constant

go
v1alpha1.ClientPort = "_client"

Purpose

  • Create Kubernetes resources (Services, Ingresses)
  • Expose HTTP ports
  • Manage TinySignals
  • Access cluster resources

Port Definition

go
{
    Name:          v1alpha1.ClientPort,
    Label:         "Client",
    Position:      module.PositionTop,
    Source:        true,
    Configuration: resource.Manager{},
}

Message Type

*resource.Manager - resource manager instance

Handler Pattern

go
func (c *Component) Handle(ctx context.Context, output Handler, port string, msg any) error {
    switch port {
    case v1alpha1.ClientPort:
        c.resourceManager = msg.(*resource.Manager)
        return nil
    }
}

Resource Manager Methods

go
// Expose HTTP port via Service + Ingress
err := c.resourceManager.ExposePort(ctx, resource.ExposePortRequest{
    Port:      8080,
    Hostnames: []string{"api.example.com"},
})

// Create a TinySignal to trigger another node
err := c.resourceManager.CreateSignal(ctx, resource.SignalRequest{
    Node: "target-node",
    Port: "input",
    Data: map[string]any{"key": "value"},
})

// Get Kubernetes client
client := c.resourceManager.Client()

When Triggered

  • Component initialization
  • After settings port processed

Port Declaration Pattern

Standard component with all system ports:

go
func (c *Component) Ports() []module.Port {
    return []module.Port{
        // System ports
        {
            Name:          v1alpha1.SettingsPort,
            Label:         "Settings",
            Position:      module.PositionTop,
            Source:        true,
            Configuration: Settings{},
        },
        {
            Name:          v1alpha1.ControlPort,
            Label:         "Control",
            Position:      module.PositionTop,
            Source:        true,
            Configuration: ControlState{},
        },

        // Data ports
        {
            Name:          "input",
            Label:         "Input",
            Position:      module.PositionLeft,
            Source:        true,
            Configuration: Input{},
        },
        {
            Name:          "output",
            Label:         "Output",
            Position:      module.PositionRight,
            Source:        false,
            Configuration: Output{},
        },
    }
}

Complete Handler Pattern

go
func (c *Component) Handle(
    ctx context.Context,
    output module.Handler,
    port string,
    msg any,
) error {
    switch port {

    // Settings configuration
    case v1alpha1.SettingsPort:
        c.settings = msg.(Settings)
        return nil

    // Kubernetes reconciliation
    case v1alpha1.ReconcilePort:
        node := msg.(*v1alpha1.TinyNode)
        return c.handleReconcile(ctx, output, node)

    // Resource manager
    case v1alpha1.ClientPort:
        c.resourceManager = msg.(*resource.Manager)
        return nil

    // Data input
    case "input":
        input := msg.(Input)
        return c.processInput(ctx, output, input)

    default:
        return fmt.Errorf("unknown port: %s", port)
    }
}

// Control handled separately via ControlHandler interface
func (c *Component) HandleControl(
    ctx context.Context,
    state any,
    port string,
    msg any,
) (any, error) {
    controlState := state.(*ControlState)

    switch port {
    case "start":
        c.start()
        return &ControlState{Status: "running"}, nil
    case "stop":
        c.stop()
        return &ControlState{Status: "stopped"}, nil
    }

    return controlState, nil
}

Port Triggering Order

Typical initialization sequence:

  1. _settings - Configuration applied
  2. _client - Resource manager available
  3. _reconcile - Initial reconciliation
  4. Data ports - Ready for messages

Best Practices

1. Store System Resources

go
type Component struct {
    settings        Settings
    resourceManager *resource.Manager
    // ...
}

2. Check Leadership for Writes

go
if utils.IsLeader(ctx) {
    // Update cluster state
}

3. Handle Missing Resources

go
if c.resourceManager == nil {
    return fmt.Errorf("resource manager not initialized")
}

4. Idempotent Reconciliation

go
func (c *Component) handleReconcile(...) error {
    // Check if action needed
    if c.alreadyConfigured {
        return nil
    }
    // Perform action
}

Build flow-based applications on Kubernetes