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.
| Port | Constant | Purpose |
|---|---|---|
_settings | v1alpha1.SettingsPort | Component configuration |
_control | v1alpha1.ControlPort | UI interaction (buttons) |
_reconcile | v1alpha1.ReconcilePort | Kubernetes reconciliation |
_client | v1alpha1.ClientPort | Resource 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
truein 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:
_settings- Configuration applied_client- Resource manager available_reconcile- Initial reconciliation- 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
}